Skip to content

Commit 2677e9d

Browse files
committed
Add erlang:unique_integer
Signed-off-by: Jakub Gonet <jakub.gonet@swmansion.com>
1 parent 8eae957 commit 2677e9d

File tree

8 files changed

+218
-1
lines changed

8 files changed

+218
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- Added `lists:dropwhile/2`.
2727
- Support for `float/1` BIF.
2828
- Added `erlang:get/0` and `erlang:erase/0`.
29+
- Added `erlang:unique_integer/0` and `erlang:unique_integer/1`
2930

3031
### Fixed
3132
- ESP32: improved sntp sync speed from a cold boot.

libs/estdlib/src/erlang.erl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@
112112
term_to_binary/1,
113113
timestamp/0,
114114
universaltime/0,
115-
localtime/0
115+
localtime/0,
116+
unique_integer/0,
117+
unique_integer/1
116118
]).
117119

118120
-export_type([
@@ -1314,3 +1316,24 @@ universaltime() ->
13141316
-spec localtime() -> calendar:datetime().
13151317
localtime() ->
13161318
erlang:nif_error(undefined).
1319+
1320+
%%-----------------------------------------------------------------------------
1321+
%% @returns A unique integer
1322+
%% @doc Same as erlang:unique_integer([]).
1323+
%% @end
1324+
%%-----------------------------------------------------------------------------
1325+
-spec unique_integer() -> integer().
1326+
unique_integer() ->
1327+
erlang:nif_error(undefined).
1328+
1329+
%%-----------------------------------------------------------------------------
1330+
%% @param Options list of options.
1331+
%% @returns a unique integer
1332+
%% @doc Return a unique integer. If positive is passed, returned integer is
1333+
%% positive. If monotonic is passed, returned integer is monotonically increasing
1334+
%% across all processes.
1335+
%% @end
1336+
%%-----------------------------------------------------------------------------
1337+
-spec unique_integer([monotonic | positive]) -> integer().
1338+
unique_integer(_Options) ->
1339+
erlang:nif_error(undefined).

src/libAtomVM/bif.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "dictionary.h"
3030
#include "interop.h"
3131
#include "overflow_helpers.h"
32+
#include "smp.h"
3233
#include "term.h"
3334
#include "trace.h"
3435
#include "unicode.h"
@@ -343,6 +344,74 @@ term bif_erlang_map_get_2(Context *ctx, uint32_t fail_label, term arg1, term arg
343344
return term_get_map_value(arg2, pos);
344345
}
345346

347+
#ifdef AVM_NO_SMP
348+
static int64_t get_unique_monotonic_integer(void)
349+
{
350+
static int64_t unique = 0;
351+
352+
if (UNLIKELY(unique == INT64_MAX)) {
353+
AVM_ABORT();
354+
}
355+
return unique++;
356+
}
357+
358+
#elif ATOMIC_LLONG_LOCK_FREE == 2
359+
360+
static int64_t get_unique_monotonic_integer(void)
361+
{
362+
static int64_t ATOMIC unique = 0;
363+
364+
if (UNLIKELY(unique == INT64_MAX)) {
365+
AVM_ABORT();
366+
}
367+
return unique++;
368+
}
369+
#else
370+
371+
static int64_t get_unique_monotonic_integer(void)
372+
{
373+
static int64_t unique = 0;
374+
static SpinLock unique_spinlock;
375+
376+
if (UNLIKELY(unique == INT64_MAX)) {
377+
AVM_ABORT();
378+
}
379+
380+
smp_spinlock_lock(&unique_spinlock);
381+
int64_t value = unique++;
382+
smp_spinlock_unlock(&unique_spinlock);
383+
return value;
384+
}
385+
386+
#endif
387+
388+
term bif_erlang_unique_integer_0(Context *ctx)
389+
{
390+
int64_t value = get_unique_monotonic_integer();
391+
return term_make_maybe_boxed_int64(value, &ctx->heap);
392+
}
393+
394+
term bif_erlang_unique_integer_1(Context *ctx, uint32_t fail_label, term arg1)
395+
{
396+
int proper = 0;
397+
if (UNLIKELY(!term_is_list(arg1))) {
398+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
399+
}
400+
size_t _len = term_list_length(arg1, &proper);
401+
UNUSED(_len);
402+
if (UNLIKELY(!proper)) {
403+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
404+
}
405+
406+
// List is checked only for correctness if in the future
407+
// we would like to handle monotonic and positive integers separately.
408+
//
409+
// Right now the implementation is backed by increasing counter
410+
// that always covers both options
411+
int64_t value = get_unique_monotonic_integer();
412+
return term_make_maybe_boxed_int64(value, &ctx->heap);
413+
}
414+
346415
static inline term make_boxed_int(Context *ctx, uint32_t fail_label, uint32_t live, avm_int_t value)
347416
{
348417
if (UNLIKELY(memory_ensure_free_with_roots(ctx, BOXED_INT_SIZE, live, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {

src/libAtomVM/bif.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ term bif_erlang_tuple_size_1(Context *ctx, uint32_t fail_label, term arg1);
6969
term bif_erlang_map_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
7070
term bif_erlang_map_get_2(Context *ctx, uint32_t fail_label, term arg1, term arg2);
7171

72+
term bif_erlang_unique_integer_0(Context *ctx);
73+
term bif_erlang_unique_integer_1(Context *ctx, uint32_t fail_label, term arg1);
74+
7275
term bif_erlang_add_2(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2);
7376
term bif_erlang_plus_1(Context *ctx, uint32_t fail_label, int live, term arg1);
7477
term bif_erlang_sub_2(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2);

src/libAtomVM/bifs.gperf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ erlang:element/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_
9191
erlang:tuple_size/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_tuple_size_1}
9292
erlang:map_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_map_size_1}
9393
erlang:map_get/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_map_get_2}
94+
erlang:unique_integer/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_unique_integer_0}
95+
erlang:unique_integer/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_unique_integer_1}
9496
erlang:min/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_min_2}
9597
erlang:max/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_max_2}
9698
erlang:size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_size_1}

tests/erlang_tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ compile_erlang(float_decode)
509509
compile_erlang(test_utf8_atoms)
510510

511511
compile_erlang(twentyone_param_function)
512+
compile_erlang(unique)
512513
compile_erlang(complex_list_match_xregs)
513514
compile_erlang(twentyone_param_fun)
514515
compile_erlang(gc_safe_x_reg_write)
@@ -995,6 +996,7 @@ add_custom_target(erlang_test_modules DEPENDS
995996
test_utf8_atoms.beam
996997

997998
twentyone_param_function.beam
999+
unique.beam
9981000
complex_list_match_xregs.beam
9991001
twentyone_param_fun.beam
10001002
gc_safe_x_reg_write.beam

tests/erlang_tests/unique.erl

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2024 Jakub Gonet <jakub.gonet@swmansion.com>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(unique).
22+
23+
-export([start/0]).
24+
25+
start() ->
26+
ok = unique_0(),
27+
ok = unique_positive(),
28+
ok = unique_monotonic(),
29+
ok = unique_positive_monotonic(),
30+
ok = unique_monotonic_processes(),
31+
32+
Self = self(),
33+
N = length([
34+
spawn_opt(fun() -> Self ! {ok, unique_0()} end, []),
35+
spawn_opt(fun() -> Self ! {ok, unique_positive()} end, []),
36+
spawn_opt(fun() -> Self ! {ok, unique_monotonic()} end, []),
37+
spawn_opt(fun() -> Self ! {ok, unique_positive_monotonic()} end, [])
38+
]),
39+
receive_messages(N),
40+
0.
41+
42+
unique_0() ->
43+
A = erlang:unique_integer(),
44+
B = erlang:unique_integer(),
45+
C = erlang:unique_integer(),
46+
Valid = (A =/= B) and (A =/= C),
47+
true = Valid,
48+
ok.
49+
50+
unique_positive() ->
51+
A = erlang:unique_integer([positive]),
52+
B = erlang:unique_integer([positive]),
53+
C = erlang:unique_integer([positive]),
54+
Valid = (A > 0) and (B > 0) and (C > 0) and (A =/= B) and (B =/= C),
55+
true = Valid,
56+
ok.
57+
58+
unique_monotonic() ->
59+
A = erlang:unique_integer([monotonic]),
60+
B = erlang:unique_integer([monotonic]),
61+
C = erlang:unique_integer([monotonic]),
62+
Valid = (A < B) and (B < C),
63+
true = Valid,
64+
ok.
65+
66+
unique_positive_monotonic() ->
67+
A = erlang:unique_integer([positive, monotonic]),
68+
B = erlang:unique_integer([positive, monotonic]),
69+
C = erlang:unique_integer([positive, monotonic]),
70+
Valid = (A > 0) and (A < B) and (B < C),
71+
true = Valid,
72+
ok.
73+
74+
unique_monotonic_processes() ->
75+
Self = self(),
76+
spawn_opt(
77+
fun() ->
78+
n_times(50, fun() -> Self ! {a, erlang:unique_integer([monotonic])} end)
79+
end,
80+
[]
81+
),
82+
spawn_opt(
83+
fun() ->
84+
n_times(100, fun() -> Self ! {b, erlang:unique_integer([monotonic])} end)
85+
end,
86+
[]
87+
),
88+
Msgs = receive_messages(150),
89+
NumA = [Num || {a, Num} <- Msgs],
90+
NumB = [Num || {b, Num} <- Msgs],
91+
true = increasing(NumA),
92+
true = increasing(NumB),
93+
ok.
94+
95+
receive_messages(N) ->
96+
receive_messages(N, []).
97+
receive_messages(0, Msgs) ->
98+
reverse(Msgs);
99+
receive_messages(N, Msgs) ->
100+
receive
101+
Msg -> receive_messages(N - 1, [Msg | Msgs])
102+
end.
103+
104+
reverse(List) -> reverse(List, []).
105+
reverse([], Acc) -> Acc;
106+
reverse([H | T], Acc) -> reverse(T, [H | Acc]).
107+
108+
n_times(0, _F) ->
109+
ok;
110+
n_times(N, F) ->
111+
F(),
112+
n_times(N - 1, F).
113+
114+
increasing([_]) -> true;
115+
increasing([A, B | T]) when A < B -> increasing([B | T]);
116+
increasing(_) -> false.

tests/test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ struct Test tests[] = {
568568
TEST_CASE(test_utf8_atoms),
569569

570570
TEST_CASE(twentyone_param_function),
571+
TEST_CASE(unique),
571572
TEST_CASE(complex_list_match_xregs),
572573
TEST_CASE(twentyone_param_fun),
573574
TEST_CASE(gc_safe_x_reg_write),

0 commit comments

Comments
 (0)