Skip to content

Commit b316e88

Browse files
committed
Merge pull request atomvm#498 from bettio/exact-equal
Actual support for exact equal (=:= and =/=) This PR adds an additional parameter that allows to perform an exact comparison (=:=) instead of a numeric comparison (regular ==). Maps comparison semantic still needs to be fixed and there might be some other minor quirks that need to be handled. 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 b620e2c + 1d41ce8 commit b316e88

File tree

8 files changed

+108
-61
lines changed

8 files changed

+108
-61
lines changed

src/libAtomVM/bif.c

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,7 +1331,7 @@ term bif_erlang_xor_2(Context *ctx, term arg1, term arg2)
13311331

13321332
term bif_erlang_equal_to_2(Context *ctx, term arg1, term arg2)
13331333
{
1334-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1334+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
13351335
if (result == TermEquals) {
13361336
return TRUE_ATOM;
13371337
} else if (result & (TermLessThan | TermGreaterThan)) {
@@ -1343,9 +1343,7 @@ term bif_erlang_equal_to_2(Context *ctx, term arg1, term arg2)
13431343

13441344
term bif_erlang_not_equal_to_2(Context *ctx, term arg1, term arg2)
13451345
{
1346-
//TODO: fix this implementation
1347-
//it should compare any kind of type, and 5.0 != 5 is false
1348-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1346+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
13491347
if (result & (TermLessThan | TermGreaterThan)) {
13501348
return TRUE_ATOM;
13511349
} else if (result == TermEquals) {
@@ -1358,7 +1356,7 @@ term bif_erlang_not_equal_to_2(Context *ctx, term arg1, term arg2)
13581356
term bif_erlang_exactly_equal_to_2(Context *ctx, term arg1, term arg2)
13591357
{
13601358
//TODO: 5.0 != 5
1361-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1359+
TermCompareResult result = term_compare(arg1, arg2, TermCompareExact, ctx->global);
13621360
if (result == TermEquals) {
13631361
return TRUE_ATOM;
13641362
} else if (result & (TermLessThan | TermGreaterThan)) {
@@ -1370,8 +1368,7 @@ term bif_erlang_exactly_equal_to_2(Context *ctx, term arg1, term arg2)
13701368

13711369
term bif_erlang_exactly_not_equal_to_2(Context *ctx, term arg1, term arg2)
13721370
{
1373-
//TODO: 5.0 != 5
1374-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1371+
TermCompareResult result = term_compare(arg1, arg2, TermCompareExact, ctx->global);
13751372
if (result & (TermLessThan | TermGreaterThan)) {
13761373
return TRUE_ATOM;
13771374
} else if (result == TermEquals) {
@@ -1383,7 +1380,7 @@ term bif_erlang_exactly_not_equal_to_2(Context *ctx, term arg1, term arg2)
13831380

13841381
term bif_erlang_greater_than_2(Context *ctx, term arg1, term arg2)
13851382
{
1386-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1383+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
13871384
if (result == TermGreaterThan) {
13881385
return TRUE_ATOM;
13891386
} else if (result & (TermEquals | TermLessThan)) {
@@ -1395,7 +1392,7 @@ term bif_erlang_greater_than_2(Context *ctx, term arg1, term arg2)
13951392

13961393
term bif_erlang_less_than_2(Context *ctx, term arg1, term arg2)
13971394
{
1398-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1395+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
13991396
if (result == TermLessThan) {
14001397
return TRUE_ATOM;
14011398
} else if (result & (TermEquals | TermGreaterThan)) {
@@ -1407,7 +1404,7 @@ term bif_erlang_less_than_2(Context *ctx, term arg1, term arg2)
14071404

14081405
term bif_erlang_less_than_or_equal_2(Context *ctx, term arg1, term arg2)
14091406
{
1410-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1407+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
14111408
if (result & (TermLessThan | TermEquals)) {
14121409
return TRUE_ATOM;
14131410
} else if (result == TermGreaterThan) {
@@ -1419,7 +1416,7 @@ term bif_erlang_less_than_or_equal_2(Context *ctx, term arg1, term arg2)
14191416

14201417
term bif_erlang_greater_than_or_equal_2(Context *ctx, term arg1, term arg2)
14211418
{
1422-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
1419+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
14231420
if (result & (TermGreaterThan | TermEquals)) {
14241421
return TRUE_ATOM;
14251422
} else if (result & TermLessThan) {

src/libAtomVM/dictionary.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static DictionaryFunctionResult dictionary_find(
3232
struct ListHead *item;
3333
LIST_FOR_EACH (item, dictionary) {
3434
struct DictEntry *entry = GET_LIST_ENTRY(item, struct DictEntry, head);
35-
TermCompareResult result = term_compare(entry->key, key, global);
35+
TermCompareResult result = term_compare(entry->key, key, TermCompareExact, global);
3636
if (result == TermEquals) {
3737
*found = entry;
3838
return DictionaryOk;

src/libAtomVM/opcodesswitch.h

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,8 @@ static bool sort_kv_pairs(struct kv_pair *kv, int size, GlobalContext *global)
860860
for (int i = 1; i < k; i++) {
861861
term t_max = kv[max_pos].key;
862862
term t = kv[i].key;
863-
TermCompareResult result = term_compare(t, t_max, global);
863+
// TODO: not sure if exact is the right choice here
864+
TermCompareResult result = term_compare(t, t_max, TermCompareExact, global);
864865
if (result == TermGreaterThan) {
865866
max_pos = i;
866867
} else if (UNLIKELY(result == TermCompareMemoryAllocFail)) {
@@ -2182,7 +2183,7 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
21822183
#ifdef IMPL_EXECUTE_LOOP
21832184
TRACE("is_lt/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
21842185

2185-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
2186+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
21862187
if (result == TermLessThan) {
21872188
NEXT_INSTRUCTION(next_off);
21882189
} else if (result & (TermGreaterThan | TermEquals)) {
@@ -2214,7 +2215,7 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
22142215
#ifdef IMPL_EXECUTE_LOOP
22152216
TRACE("is_ge/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
22162217

2217-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
2218+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
22182219
if (result & (TermGreaterThan | TermEquals)) {
22192220
NEXT_INSTRUCTION(next_off);
22202221
} else if (result == TermLessThan) {
@@ -2246,8 +2247,7 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
22462247
#ifdef IMPL_EXECUTE_LOOP
22472248
TRACE("is_equal/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
22482249

2249-
//TODO: implement this
2250-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
2250+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
22512251
if (result == TermEquals) {
22522252
NEXT_INSTRUCTION(next_off);
22532253
} else if (result & (TermLessThan | TermGreaterThan)) {
@@ -2279,7 +2279,7 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
22792279
#ifdef IMPL_EXECUTE_LOOP
22802280
TRACE("is_not_equal/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
22812281

2282-
TermCompareResult result = term_compare(arg1, arg2, ctx->global);
2282+
TermCompareResult result = term_compare(arg1, arg2, TermCompareNoOpts, ctx->global);
22832283
if (result & (TermLessThan | TermGreaterThan)) {
22842284
NEXT_INSTRUCTION(next_off);
22852285
} else if (result == TermEquals) {
@@ -2311,12 +2311,13 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
23112311
#ifdef IMPL_EXECUTE_LOOP
23122312
TRACE("is_eq_exact/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
23132313

2314-
// handle error
2315-
//TODO: implement this
2316-
if (term_exactly_equals(arg1, arg2, ctx->global)) {
2314+
TermCompareResult result = term_compare(arg1, arg2, TermCompareExact, ctx->global);
2315+
if (result == TermEquals) {
23172316
NEXT_INSTRUCTION(next_off);
2318-
} else {
2317+
} else if (result & (TermLessThan | TermGreaterThan)) {
23192318
i = POINTER_TO_II(mod->labels[label]);
2319+
} else {
2320+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
23202321
}
23212322
#endif
23222323

@@ -2342,12 +2343,13 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
23422343
#ifdef IMPL_EXECUTE_LOOP
23432344
TRACE("is_not_eq_exact/2, label=%i, arg1=%lx, arg2=%lx\n", label, arg1, arg2);
23442345

2345-
// handle error
2346-
//TODO: implement this
2347-
if (!term_exactly_equals(arg1, arg2, ctx->global)) {
2346+
TermCompareResult result = term_compare(arg1, arg2, TermCompareExact, ctx->global);
2347+
if (result & (TermLessThan | TermGreaterThan)) {
23482348
NEXT_INSTRUCTION(next_off);
2349-
} else {
2349+
} else if (result == TermEquals) {
23502350
i = POINTER_TO_II(mod->labels[label]);
2351+
} else {
2352+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
23512353
}
23522354
#endif
23532355

@@ -5414,7 +5416,8 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
54145416
} else {
54155417
term src_key = term_get_map_key(src, src_pos);
54165418
term new_key = kv[kv_pos].key;
5417-
switch (term_compare(src_key, new_key, ctx->global)) {
5419+
// TODO: not sure if exact is the right choice here
5420+
switch (term_compare(src_key, new_key, TermCompareExact, ctx->global)) {
54185421
case TermLessThan: {
54195422
term src_value = term_get_map_value(src, src_pos);
54205423
term_set_map_assoc(map, j, src_key, src_value);
@@ -6573,9 +6576,12 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
65736576
DECODE_COMPACT_TERM(update_value, code, i, next_off);
65746577
#ifdef IMPL_EXECUTE_LOOP
65756578
if (reuse) {
6576-
// handle error
6577-
if (term_exactly_equals(update_value, term_get_tuple_element(dst, update_ix - 1), ctx->global)) {
6579+
term old_value = term_get_tuple_element(dst, update_ix - 1);
6580+
TermCompareResult result = term_compare(update_value, old_value, TermCompareExact, ctx->global);
6581+
if (result == TermEquals) {
65786582
continue;
6583+
} else if (UNLIKELY(result == TermCompareMemoryAllocFail)) {
6584+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
65796585
}
65806586
reuse = false;
65816587
}

src/libAtomVM/term.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,18 +369,21 @@ static int term_type_to_index(term t)
369369
if (term_is_invalid_term(t)) {
370370
return 0;
371371

372-
} else if (term_is_number(t)) {
372+
} else if (term_is_any_integer(t)) {
373373
return 1;
374374

375-
} else if (term_is_atom(t)) {
375+
} else if (term_is_float(t)) {
376376
return 2;
377377

378-
} else if (term_is_reference(t)) {
378+
} else if (term_is_atom(t)) {
379379
return 3;
380380

381-
} else if (term_is_function(t)) {
381+
} else if (term_is_reference(t)) {
382382
return 4;
383383

384+
} else if (term_is_function(t)) {
385+
return 5;
386+
384387
} else if (term_is_pid(t)) {
385388
return 6;
386389

@@ -404,7 +407,7 @@ static int term_type_to_index(term t)
404407
}
405408
}
406409

407-
TermCompareResult term_compare(term t, term other, GlobalContext *global)
410+
TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalContext *global)
408411
{
409412
struct TempStack temp_stack;
410413
if (UNLIKELY(temp_stack_init(&temp_stack) != TempStackOk)) {
@@ -560,7 +563,18 @@ TermCompareResult term_compare(term t, term other, GlobalContext *global)
560563
break;
561564
}
562565

563-
} else if (term_is_number(t) && term_is_number(other)) {
566+
} else if (term_is_float(t) && term_is_float(other)) {
567+
avm_float_t t_float = term_to_float(t);
568+
avm_float_t other_float = term_to_float(other);
569+
if (t_float == other_float) {
570+
other = temp_stack_pop(&temp_stack);
571+
t = temp_stack_pop(&temp_stack);
572+
} else {
573+
result = (t_float > other_float) ? TermGreaterThan : TermLessThan;
574+
break;
575+
}
576+
577+
} else if (term_is_number(t) && term_is_number(other) && ((opts & TermCompareExact) != TermCompareExact)) {
564578
avm_float_t t_float = term_conv_to_float(t);
565579
avm_float_t other_float = term_conv_to_float(other);
566580
if (t_float == other_float) {
@@ -570,6 +584,7 @@ TermCompareResult term_compare(term t, term other, GlobalContext *global)
570584
result = (t_float > other_float) ? TermGreaterThan : TermLessThan;
571585
break;
572586
}
587+
573588
} else if (term_is_atom(t) && term_is_atom(other)) {
574589
int t_atom_index = term_to_atom_index(t);
575590
AtomString t_atom_string = (AtomString) valueshashtable_get_value(global->atoms_ids_table,

src/libAtomVM/term.h

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ enum RefcBinaryFlags
106106
RefcBinaryIsConst
107107
};
108108

109+
typedef enum
110+
{
111+
TermCompareNoOpts = 0,
112+
TermCompareExact = 1
113+
} TermCompareOpts;
114+
109115
typedef enum
110116
{
111117
TermCompareMemoryAllocFail = 0,
@@ -131,7 +137,7 @@ extern const term empty_tuple;
131137
* @param global the global context
132138
* @return any of TermEquals, TermLessThan, TermGreaterThan or TermCompareMemoryAllocFail error.
133139
*/
134-
TermCompareResult term_compare(term t, term other, GlobalContext *global);
140+
TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalContext *global);
135141

136142
/**
137143
* @brief Create a reference-counted binary on the heap
@@ -1338,30 +1344,6 @@ static inline int term_list_length(term t, int *proper)
13381344
return len;
13391345
}
13401346

1341-
/**
1342-
* @brief Returns 1 if given terms are exactly equal.
1343-
*
1344-
* @details Compares 2 given terms and returns 1 if they are the same.
1345-
* @param a first term
1346-
* @param b second term
1347-
* @return 1 if they are the same, 0 otherwise.
1348-
*/
1349-
static inline int term_exactly_equals(term a, term b, GlobalContext *global)
1350-
{
1351-
if (a == b) {
1352-
return 1;
1353-
} else {
1354-
TermCompareResult result = term_compare(a, b, global);
1355-
if (result == TermEquals) {
1356-
return 1;
1357-
} else if (UNLIKELY(result == TermCompareMemoryAllocFail)) {
1358-
AVM_ABORT();
1359-
} else {
1360-
return 0;
1361-
}
1362-
}
1363-
}
1364-
13651347
static inline int term_is_float(term t)
13661348
{
13671349
if (term_is_boxed(t)) {
@@ -1656,7 +1638,8 @@ static inline int term_find_map_pos(term map, term key, GlobalContext *global)
16561638
int arity = term_get_tuple_arity(keys);
16571639
for (int i = 0; i < arity; ++i) {
16581640
term k = term_get_tuple_element(keys, i);
1659-
TermCompareResult result = term_compare(key, k, global);
1641+
// TODO: not sure if exact is the right choice here
1642+
TermCompareResult result = term_compare(key, k, TermCompareExact, global);
16601643
if (result == TermEquals) {
16611644
return i;
16621645
} else if (UNLIKELY(result == TermCompareMemoryAllocFail)) {

tests/erlang_tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ compile_erlang(bs_append_extra_words)
427427

428428
compile_erlang(test_monotonic_time)
429429

430+
compile_erlang(exactly_eq)
431+
430432
compile_erlang(spawn_opt_monitor_normal)
431433
compile_erlang(spawn_opt_monitor_throw)
432434
compile_erlang(spawn_opt_demonitor_normal)
@@ -842,6 +844,8 @@ add_custom_target(erlang_test_modules DEPENDS
842844

843845
test_monotonic_time.beam
844846

847+
exactly_eq.beam
848+
845849
spawn_opt_monitor_normal.beam
846850
spawn_opt_monitor_throw.beam
847851
spawn_opt_demonitor_normal.beam

tests/erlang_tests/exactly_eq.erl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2023 Davide Bettio <[email protected]>
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(exactly_eq).
22+
23+
-export([start/0, fact/3]).
24+
25+
start() ->
26+
A = fact(4, 1, 1),
27+
B = fact(4, 1.0, 1.0),
28+
test(not (A =:= B)) +
29+
test(A =/= B) * 2 +
30+
test(A == B) * 4.
31+
32+
test(true) ->
33+
1;
34+
test(false) ->
35+
0.
36+
37+
fact(N, _D, Acc) when N < 1 ->
38+
Acc;
39+
fact(N, D, Acc) ->
40+
fact(N - D, D, Acc * N).

tests/test.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ struct Test tests[] = {
454454

455455
TEST_CASE_EXPECTED(test_monotonic_time, 1),
456456

457+
TEST_CASE_EXPECTED(exactly_eq, 7),
458+
457459
// Tests relying on echo driver
458460
TEST_CASE_ATOMVM_ONLY(pingpong, 1),
459461

0 commit comments

Comments
 (0)