Skip to content

Commit 0848033

Browse files
committed
Merge pull request #1597 from bettio/continue-unique-integer-avm
See also #1519 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 bcab8ac + 8f9dcbf commit 0848033

File tree

8 files changed

+191
-1
lines changed

8 files changed

+191
-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: 28 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"
@@ -387,6 +388,33 @@ term bif_erlang_map_get_2(Context *ctx, uint32_t fail_label, term arg1, term arg
387388
return term_get_map_value(arg2, pos);
388389
}
389390

391+
term bif_erlang_unique_integer_0(Context *ctx)
392+
{
393+
int64_t value = globalcontext_get_ref_ticks(ctx->global);
394+
return term_make_maybe_boxed_int64(value, &ctx->heap);
395+
}
396+
397+
term bif_erlang_unique_integer_1(Context *ctx, uint32_t fail_label, term arg1)
398+
{
399+
int proper = 0;
400+
if (UNLIKELY(!term_is_list(arg1))) {
401+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
402+
}
403+
size_t _len = term_list_length(arg1, &proper);
404+
UNUSED(_len);
405+
if (UNLIKELY(!proper)) {
406+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
407+
}
408+
409+
// List is checked only for correctness if in the future
410+
// we would like to handle monotonic and positive integers separately.
411+
//
412+
// Right now the implementation is backed by increasing counter
413+
// that always covers both options
414+
int64_t value = globalcontext_get_ref_ticks(ctx->global);
415+
return term_make_maybe_boxed_int64(value, &ctx->heap);
416+
}
417+
390418
static inline term make_boxed_int(Context *ctx, uint32_t fail_label, uint32_t live, avm_int_t value)
391419
{
392420
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
@@ -70,6 +70,9 @@ term bif_erlang_tuple_size_1(Context *ctx, uint32_t fail_label, term arg1);
7070
term bif_erlang_map_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
7171
term bif_erlang_map_get_2(Context *ctx, uint32_t fail_label, term arg1, term arg2);
7272

73+
term bif_erlang_unique_integer_0(Context *ctx);
74+
term bif_erlang_unique_integer_1(Context *ctx, uint32_t fail_label, term arg1);
75+
7376
term bif_erlang_add_2(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2);
7477
term bif_erlang_plus_1(Context *ctx, uint32_t fail_label, int live, term arg1);
7578
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
@@ -92,6 +92,8 @@ erlang:element/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_
9292
erlang:tuple_size/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_tuple_size_1}
9393
erlang:map_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_map_size_1}
9494
erlang:map_get/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_map_get_2}
95+
erlang:unique_integer/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_unique_integer_0}
96+
erlang:unique_integer/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_unique_integer_1}
9597
erlang:min/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_min_2}
9698
erlang:max/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_max_2}
9799
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
@@ -510,6 +510,7 @@ compile_erlang(float_decode)
510510
compile_erlang(test_utf8_atoms)
511511

512512
compile_erlang(twentyone_param_function)
513+
compile_erlang(unique)
513514
compile_erlang(complex_list_match_xregs)
514515
compile_erlang(twentyone_param_fun)
515516
compile_erlang(gc_safe_x_reg_write)
@@ -997,6 +998,7 @@ add_custom_target(erlang_test_modules DEPENDS
997998
test_utf8_atoms.beam
998999

9991000
twentyone_param_function.beam
1001+
unique.beam
10001002
complex_list_match_xregs.beam
10011003
twentyone_param_fun.beam
10021004
gc_safe_x_reg_write.beam

tests/erlang_tests/unique.erl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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, id/1, check_error/3]).
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+
test_invalid() ->
43+
ok = ?MODULE:check_error(fun() -> erlang:unique_integer(?MODULE:id(5)) end),
44+
ok = ?MODULE:check_error(fun() -> erlang:unique_integer(?MODULE:id([positive | monotonic])) end).
45+
46+
check_error(F, A, B) ->
47+
try F() of
48+
Result -> {unexpected, Result}
49+
catch
50+
A:B -> ok
51+
end.
52+
53+
id(X) ->
54+
X.
55+
56+
unique_0() ->
57+
A = erlang:unique_integer(),
58+
B = erlang:unique_integer(),
59+
C = erlang:unique_integer(),
60+
Valid = (A =/= B) and (A =/= C),
61+
true = Valid,
62+
ok.
63+
64+
unique_positive() ->
65+
A = erlang:unique_integer([positive]),
66+
B = erlang:unique_integer([positive]),
67+
C = erlang:unique_integer([positive]),
68+
Valid = (A > 0) and (B > 0) and (C > 0) and (A =/= B) and (B =/= C),
69+
true = Valid,
70+
ok.
71+
72+
unique_monotonic() ->
73+
A = erlang:unique_integer([monotonic]),
74+
B = erlang:unique_integer([monotonic]),
75+
C = erlang:unique_integer([monotonic]),
76+
Valid = (A < B) and (B < C),
77+
true = Valid,
78+
ok.
79+
80+
unique_positive_monotonic() ->
81+
A = erlang:unique_integer([positive, monotonic]),
82+
B = erlang:unique_integer([positive, monotonic]),
83+
C = erlang:unique_integer([positive, monotonic]),
84+
Valid = (A > 0) and (A < B) and (B < C),
85+
true = Valid,
86+
ok.
87+
88+
unique_monotonic_processes() ->
89+
Self = self(),
90+
spawn_opt(
91+
fun() ->
92+
n_times(50, fun() -> Self ! {a, erlang:unique_integer([monotonic])} end)
93+
end,
94+
[]
95+
),
96+
spawn_opt(
97+
fun() ->
98+
n_times(100, fun() -> Self ! {b, erlang:unique_integer([monotonic])} end)
99+
end,
100+
[]
101+
),
102+
Msgs = receive_messages(150),
103+
NumA = [Num || {a, Num} <- Msgs],
104+
NumB = [Num || {b, Num} <- Msgs],
105+
true = increasing(NumA),
106+
true = increasing(NumB),
107+
ok.
108+
109+
receive_messages(N) ->
110+
receive_messages(N, []).
111+
receive_messages(0, Msgs) ->
112+
reverse(Msgs);
113+
receive_messages(N, Msgs) ->
114+
receive
115+
Msg -> receive_messages(N - 1, [Msg | Msgs])
116+
end.
117+
118+
reverse(List) -> reverse(List, []).
119+
reverse([], Acc) -> Acc;
120+
reverse([H | T], Acc) -> reverse(T, [H | Acc]).
121+
122+
n_times(0, _F) ->
123+
ok;
124+
n_times(N, F) ->
125+
F(),
126+
n_times(N - 1, F).
127+
128+
increasing([_]) -> true;
129+
increasing([A, B | T]) when A < B -> increasing([B | T]);
130+
increasing(_) -> false.

tests/test.c

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

571571
TEST_CASE(twentyone_param_function),
572+
TEST_CASE(unique),
572573
TEST_CASE(complex_list_match_xregs),
573574
TEST_CASE(twentyone_param_fun),
574575
TEST_CASE(gc_safe_x_reg_write),

0 commit comments

Comments
 (0)