Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added DWARF debug information support for JIT-compiled code
- Added I2C and SPI APIs to rp2 platform
- Added `code:get_object_code/1`
- Added support for integer parts-per-second timeunit

### Changed
- ~10% binary size reduction by rewriting module loading logic
Expand Down
2 changes: 1 addition & 1 deletion libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
-type atom_encoding() :: latin1 | utf8 | unicode.

-type mem_type() :: binary.
-type time_unit() :: second | millisecond | microsecond | nanosecond | native.
-type time_unit() :: second | millisecond | microsecond | nanosecond | native | pos_integer().
-type timestamp() :: {
MegaSecs :: non_neg_integer(), Secs :: non_neg_integer(), MicroSecs :: non_neg_integer
}.
Expand Down
1 change: 1 addition & 0 deletions libs/exavmlib/lib/System.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule System do
| :millisecond
| :microsecond
| :nanosecond
| pos_integer()

@doc """
Returns the current monotonic time in the `:native` time unit.
Expand Down
63 changes: 62 additions & 1 deletion src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,30 @@ term nif_erlang_monotonic_time_1(Context *ctx, int argc, term argv[])
} else if (unit == NANOSECOND_ATOM || unit == NATIVE_ATOM) {
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * INT64_C(1000000000) + ts.tv_nsec);

} else if (term_is_int64(unit)) {
avm_int64_t parts_per_second = term_maybe_unbox_int64(unit);
if (UNLIKELY(parts_per_second <= 0)) {
RAISE_ERROR(BADARG_ATOM);
}
if (UNLIKELY(
((ts.tv_sec > 0) && ((avm_int64_t) ts.tv_sec > INT64_MAX / parts_per_second))
|| ((ts.tv_sec < 0) && ((avm_int64_t) ts.tv_sec < INT64_MIN / parts_per_second)))) {
RAISE_ERROR(BADARG_ATOM);
}
avm_int64_t second_part = (avm_int64_t) ts.tv_sec * parts_per_second;
avm_int64_t quotient = parts_per_second / INT64_C(1000000000);
avm_int64_t remainder = parts_per_second % INT64_C(1000000000);
avm_int64_t fractional_high = (avm_int64_t) ts.tv_nsec * quotient;
avm_int64_t fractional_low = ((avm_int64_t) ts.tv_nsec * remainder) / INT64_C(1000000000);
if (UNLIKELY(fractional_high > INT64_MAX - fractional_low)) {
RAISE_ERROR(BADARG_ATOM);
}
avm_int64_t fractional_part = fractional_high + fractional_low;
if (UNLIKELY(second_part > INT64_MAX - fractional_part)) {
RAISE_ERROR(BADARG_ATOM);
}
return make_maybe_boxed_int64(ctx, second_part + fractional_part);

} else {
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -1891,6 +1915,30 @@ term nif_erlang_system_time_1(Context *ctx, int argc, term argv[])
} else if (unit == NANOSECOND_ATOM || unit == NATIVE_ATOM) {
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * INT64_C(1000000000) + ts.tv_nsec);

} else if (term_is_int64(unit)) {
avm_int64_t parts_per_second = term_maybe_unbox_int64(unit);
if (UNLIKELY(parts_per_second <= 0)) {
RAISE_ERROR(BADARG_ATOM);
}
if (UNLIKELY(
((ts.tv_sec > 0) && ((avm_int64_t) ts.tv_sec > INT64_MAX / parts_per_second))
|| ((ts.tv_sec < 0) && ((avm_int64_t) ts.tv_sec < INT64_MIN / parts_per_second)))) {
RAISE_ERROR(BADARG_ATOM);
}
avm_int64_t second_part = (avm_int64_t) ts.tv_sec * parts_per_second;
avm_int64_t quotient = parts_per_second / INT64_C(1000000000);
avm_int64_t remainder = parts_per_second % INT64_C(1000000000);
avm_int64_t fractional_high = (avm_int64_t) ts.tv_nsec * quotient;
avm_int64_t fractional_low = ((avm_int64_t) ts.tv_nsec * remainder) / INT64_C(1000000000);
if (UNLIKELY(fractional_high > INT64_MAX - fractional_low)) {
RAISE_ERROR(BADARG_ATOM);
}
avm_int64_t fractional_part = fractional_high + fractional_low;
if (UNLIKELY(second_part > INT64_MAX - fractional_part)) {
RAISE_ERROR(BADARG_ATOM);
}
return make_maybe_boxed_int64(ctx, second_part + fractional_part);

} else {
RAISE_ERROR(BADARG_ATOM);
}
Expand Down Expand Up @@ -2004,7 +2052,6 @@ term nif_calendar_system_time_to_universal_time_2(Context *ctx, int argc, term a
UNUSED(argc);

struct timespec ts;

avm_int64_t value = term_maybe_unbox_int64(argv[0]);

if (argv[1] == SECOND_ATOM) {
Expand All @@ -2023,6 +2070,20 @@ term nif_calendar_system_time_to_universal_time_2(Context *ctx, int argc, term a
ts.tv_sec = (time_t) (value / INT64_C(1000000000));
ts.tv_nsec = value % INT64_C(1000000000);

} else if (term_is_int64(argv[1])) {
avm_int64_t parts_per_second = term_maybe_unbox_int64(argv[1]);
if (UNLIKELY(parts_per_second <= 0)) {
RAISE_ERROR(BADARG_ATOM);
}
if (UNLIKELY(!term_is_int64(argv[0]))) {
RAISE_ERROR(BADARG_ATOM);
}
ts.tv_sec = (time_t) (value / parts_per_second);
if ((value % parts_per_second) < 0) {
ts.tv_sec -= 1;
}
ts.tv_nsec = 0;

} else {
RAISE_ERROR(BADARG_ATOM);
}
Expand Down
47 changes: 47 additions & 0 deletions tests/erlang_tests/test_monotonic_time.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ start() ->
true = is_integer(N2 - N1) andalso (N2 - N1) >= 0,

ok = test_native_monotonic_time(),
ok = test_integer_time_unit(),
ok = test_bad_integer_time_unit(),

1.

Expand All @@ -46,6 +48,15 @@ test_diff(X) when is_integer(X) andalso X >= 0 ->
test_diff(X) when X < 0 ->
0.

expect(F, Expect) ->
try
F(),
fail
catch
_:E when E == Expect ->
ok
end.

test_native_monotonic_time() ->
Na1 = erlang:monotonic_time(native),
receive
Expand All @@ -54,3 +65,39 @@ test_native_monotonic_time() ->
Na2 = erlang:monotonic_time(native),
true = is_integer(Na2 - Na1) andalso (Na2 - Na1) >= 0,
ok.

test_integer_time_unit() ->
%% integer 1 = parts per second, equivalent to second
S = erlang:monotonic_time(second),
S1 = erlang:monotonic_time(1),
true = abs(S1 - S) =< 1,

%% integer 1000 = parts per second, equivalent to millisecond
Ms = erlang:monotonic_time(millisecond),
Ms1 = erlang:monotonic_time(1000),
true = abs(Ms1 - Ms) =< 1,

%% integer 1000000 = parts per second, equivalent to microsecond
Us = erlang:monotonic_time(microsecond),
Us1 = erlang:monotonic_time(1000000),
true = abs(Us1 - Us) =< 1000,

%% integer 1000000000 = parts per second, equivalent to nanosecond
Ns = erlang:monotonic_time(nanosecond),
Ns1 = erlang:monotonic_time(1000000000),
true = abs(Ns1 - Ns) =< 1000000,

%% verify monotonicity with integer unit
T1 = erlang:monotonic_time(1000),
receive
after 1 -> ok
end,
T2 = erlang:monotonic_time(1000),
true = T2 >= T1,

ok.

test_bad_integer_time_unit() ->
ok = expect(fun() -> erlang:monotonic_time(0) end, badarg),
ok = expect(fun() -> erlang:monotonic_time(-1) end, badarg),
ok.
62 changes: 62 additions & 0 deletions tests/erlang_tests/test_system_time.erl
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ start() ->
ok = test_os_system_time(),
ok = test_time_unit_ratios(),

ok = test_integer_time_unit(),
ok = test_bad_integer_time_unit(),

ok = expect(fun() -> erlang:system_time(not_a_time_unit) end, badarg),

ok = test_system_time_to_universal_time(),
ok = test_integer_unit_universal_time(),
ok = test_bad_integer_unit_universal_time(),

0.

Expand Down Expand Up @@ -165,3 +170,60 @@ test_native_universal_time() ->
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, native),
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1000000000, native),
ok.

test_integer_time_unit() ->
%% integer 1 = parts per second, equivalent to second
S = erlang:system_time(second),
S1 = erlang:system_time(1),
true = abs(S1 - S) =< 1,

%% integer 1000 = parts per second, equivalent to millisecond
Ms = erlang:system_time(millisecond),
Ms1 = erlang:system_time(1000),
true = abs(Ms1 - Ms) =< 1,

%% integer 1000000 = parts per second, equivalent to microsecond
Us = erlang:system_time(microsecond),
Us1 = erlang:system_time(1000000),
true = abs(Us1 - Us) =< 1000,

%% integer 1000000000 = parts per second, equivalent to nanosecond
Ns = erlang:system_time(nanosecond),
Ns1 = erlang:system_time(1000000000),
true = abs(Ns1 - Ns) =< 1000000,

%% verify values are positive
true = S1 > 0,
true = Ms1 > 0,
true = Us1 > 0,
true = Ns1 > 0,

ok.

test_integer_unit_universal_time() ->
%% integer 1 = seconds
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, 1),
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1, 1),
{{2023, 7, 8}, {20, 19, 39}} = calendar:system_time_to_universal_time(1688847579, 1),

%% integer 1000 = milliseconds
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, 1000),
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1000, 1000),
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1001, 1000),
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, 1000),

%% integer 1000000 = microseconds
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, 1000000),
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1000000, 1000000),
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, 1000000),

ok.

test_bad_integer_time_unit() ->
ok = expect(fun() -> erlang:system_time(0) end, badarg),
ok = expect(fun() -> erlang:system_time(-1) end, badarg),
ok.

test_bad_integer_unit_universal_time() ->
ok = expect(fun() -> calendar:system_time_to_universal_time(0, 0) end, badarg),
ok.
Loading