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
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ jobs:
run: |
./tests/test-term

- name: "Test: test-bitstring"
timeout-minutes: 10
working-directory: build
run: |
./tests/test-bitstring

- name: "Test: test_etest.avm"
timeout-minutes: 5
working-directory: build
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/build-and-test-on-freebsd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ jobs:
cd build
./tests/test-term

- name: "Test: test-bitstring"
shell: freebsd {0}
run: |
cd $GITHUB_WORKSPACE;
cd build
./tests/test-bitstring

- name: "Test: test_etest.avm"
shell: freebsd {0}
run: |
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,19 @@ jobs:
ulimit -c unlimited
./tests/test-erlang -s prime_smp

- name: "Test: test-bitstring with valgrind"
if: matrix.library-arch == ''
working-directory: build
run: |
ulimit -c unlimited
valgrind --error-exitcode=1 ./tests/test-bitstring

- name: "Test: test-bitstring"
working-directory: build
run: |
ulimit -c unlimited
./tests/test-bitstring

- name: "Test: test-enif with valgrind"
if: matrix.library-arch == ''
working-directory: build
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-linux-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,16 @@ jobs:
cp ../build_tests/tests/libs/alisp/*.avm tests/libs/alisp/ &&
VERBOSE=1 make AtomVM &&
make test-erlang &&
make test-bitstring &&
make test-enif &&
make test-heap &&
make test-mailbox &&
make test-structs &&
make test-term &&
file ./tests/test-erlang &&
./tests/test-erlang -s prime_smp &&
file ./tests/test-bitstring &&
./tests/test-bitstring &&
file ./tests/test-enif &&
./tests/test-enif &&
file ./tests/test-heap &&
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The emscripten module object gained `trackedObjectsMap`, `nextTrackedObjectKey()` and the
`onRunTrackedJs`, `onGetTrackedObjects` and `onTrackedObjectDelete` hooks, which embedders may
override to customize what tracking means
- Added support for non-byte-aligned bitstrings

### Changed
- `erlang:process_info/2` now accepts only pids of local processes, as Erlang/OTP does:
Expand Down Expand Up @@ -109,6 +110,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed `term_is_uint32` accepting big integers whose low 64 bits are within range on 32-bit
builds, which made `erlang:crc32/2`, `erlang:crc32_combine/3` and `crypto:pbkdf2_hmac/5`
silently truncate huge integer arguments instead of raising `badarg`
- Fixed `erlang:list_to_binary/1`, `erlang:iolist_to_binary/1`, `erlang:iolist_size/1` and the
`unicode:characters_to_*` functions accepting an integer improper tail, such as `[1 | 2]`

## [0.7.0-alpha.1] - 2026-04-06

Expand Down
14 changes: 13 additions & 1 deletion code-queries/allocations-exceeding-ensure-free.ql
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,14 @@ predicate consumesContextBudget(FunctionCall efCall, FunctionCall consumer) {
ensureFreeContextVar(efCall)
}

/**
* Holds if `f` tears down a context (e.g. `context_destroy`): it frees the
* context rather than re-establishing a usable heap budget on it.
*/
predicate isContextTeardown(Function f) {
f.hasName("context_destroy")
}

/**
* Holds if `efCall` is a redundant reserving ensure_free: no allocating call
* uses its budget, and `supersedingCall` is a subsequent call that resets
Expand All @@ -959,6 +967,8 @@ predicate consumesContextBudget(FunctionCall efCall, FunctionCall consumer) {
pragma[nomagic]
predicate isRedundantEnsureFree(FunctionCall efCall, FunctionCall supersedingCall) {
isReservingEnsureFreeCall(efCall) and
// Test code calls ensure_free for its GC side effects
not efCall.getFile().getRelativePath().matches("tests/%") and
// No allocating call uses this ensure_free's budget
not exists(FunctionCall a | allocToBudget(a, efCall)) and
// ...and no pointer-bumping consumer (memory_copy_term_tree) uses it either
Expand Down Expand Up @@ -986,8 +996,10 @@ predicate isRedundantEnsureFree(FunctionCall efCall, FunctionCall supersedingCal
// A function that internally calls ensure_free on the caller's
// context (e.g., enif_make_resource), passed that same context as an
// argument. Uses the ensure_free-only notion: own-heap setup does not
// reset this context's budget.
// reset this context's budget. Context teardown (context_destroy) is
// excluded: it frees the context rather than re-establishing a budget.
not isEnsureFreeCall(supersedingCall) and
not isContextTeardown(supersedingCall.getTarget()) and
transitivelyCallsEnsureFreeOnly(supersedingCall.getTarget()) and
supersedingCall.getAnArgument().(VariableAccess).getTarget() = ctxVar
) and
Expand Down
1 change: 0 additions & 1 deletion doc/src/apidocs/libatomvm/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ Functions
.. doxygenfunction:: term_binary_heap_size
.. doxygenfunction:: term_binary_size_is_heap_binary
.. doxygenfunction:: term_boxed_size
.. doxygenfunction:: term_bs_insert_binary
.. doxygenfunction:: term_compare
.. doxygenfunction:: term_create_empty_binary
.. doxygenfunction:: term_create_uninitialized_binary
Expand Down
22 changes: 15 additions & 7 deletions doc/src/differences-with-beam.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,25 @@ Since `-0` is not allowed, the result is normalized to `0`.

### Bit syntax

AtomVM supports binaries, binary construction and binary pattern matching. Bit syntax (i.e. with
sizes not a multiple of 8) is only supported as long as they would not generate bitstrings, i.e.
binaries with a number if bits that are not multiple of 8.
AtomVM supports binaries and bitstrings, including construction and pattern matching of bitstrings
whose size in bits is not a multiple of 8:

The following is supported:
<<X:3>> = <<1:3>>

<<X:3, Y:5>> = <<N>>
There is one limitation, which follows from the 256-bit integer limit described above: a segment
holding an integer that does not fit in 64 bits is only supported when it is byte-aligned, meaning
both its offset in the bitstring and its size in bits are multiples of 8. Otherwise AtomVM raises
`unsupported`, where BEAM would construct or match the segment.

The following is not:
The following is supported, as the segment starts at offset 0 and is 72 bits long:

<<X:3>>
<<X:72>> = <<(1 bsl 70):72>>

The following is not, as the 72-bit segment starts at bit offset 1:

<<_:1, X:72, _:7>> = Bin

Integers that do fit in 64 bits have no such restriction and may be used at any offset and size.

### Code reloading

Expand Down
14 changes: 9 additions & 5 deletions libs/estdlib/src/binary.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ copy(_Binary, _N) ->
%% @end
%%-----------------------------------------------------------------------------
-spec decode_hex(Data :: <<_:_*16>>) -> binary().
decode_hex(Data) ->
decode_hex(Data) when is_binary(Data) ->
case byte_size(Data) rem 2 of
0 -> <<<<(binary_to_integer(B, 16))>> || <<B:2/binary>> <= Data>>;
_ -> erlang:error(badarg)
end.
end;
decode_hex(_Data) ->
erlang:error(badarg).

%%-----------------------------------------------------------------------------
%% @param Data binary data to convert into hex encoded binary
Expand All @@ -101,10 +103,12 @@ encode_hex(Data) ->
%% @end
%%-----------------------------------------------------------------------------
-spec encode_hex(Data :: binary(), Case :: lowercase | uppercase) -> binary().
encode_hex(Data, uppercase) ->
encode_hex(Data, uppercase) when is_binary(Data) ->
<<(integer_to_binary(B, 16)) || <<B:4>> <= Data>>;
encode_hex(Data, lowercase) ->
<<<<(hd(string:to_lower(integer_to_list(B, 16)))):8>> || <<B:4>> <= Data>>.
encode_hex(Data, lowercase) when is_binary(Data) ->
<<<<(hd(string:to_lower(integer_to_list(B, 16)))):8>> || <<B:4>> <= Data>>;
encode_hex(_Data, _Case) ->
erlang:error(badarg).

%%-----------------------------------------------------------------------------
%% @param Binaries non-empty list of binaries
Expand Down
48 changes: 42 additions & 6 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
list_to_atom/1,
list_to_existing_atom/1,
list_to_binary/1,
list_to_bitstring/1,
list_to_integer/1,
list_to_integer/2,
list_to_tuple/1,
Expand Down Expand Up @@ -156,6 +157,7 @@
hd/1,
is_atom/1,
is_binary/1,
is_bitstring/1,
Comment thread
pguyot marked this conversation as resolved.
is_boolean/1,
is_float/1,
is_function/1,
Expand Down Expand Up @@ -192,11 +194,15 @@
stacktrace/0,
stacktrace_extrainfo/0,
raise_stacktrace/0,
term_to_binary_option/0
term_to_binary_option/0,
bitstring_list/0
]).

-type atom_encoding() :: latin1 | utf8 | unicode.

-type bitstring_list() ::
maybe_improper_list(byte() | bitstring() | bitstring_list(), bitstring() | []).

-type mem_type() :: binary.
-type time_unit() :: second | millisecond | microsecond | nanosecond | native.
-type timestamp() :: {
Expand Down Expand Up @@ -830,6 +836,22 @@ list_to_existing_atom(_String) ->
list_to_binary(_IOList) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param BitstringList list to convert to bitstring
%% @returns a bitstring composed of bytes and bitstrings from the list
%% @doc Convert a list into a bitstring.
%%
%% Unlike `list_to_binary/1', the elements of the list may be
%% bitstrings that are not a whole number of bytes, and so may be the
%% result.
%%
%% Errors with `badarg' if the list is not a list of bytes and bitstrings.
%% @end
%%-----------------------------------------------------------------------------
-spec list_to_bitstring(BitstringList :: bitstring_list()) -> bitstring().
list_to_bitstring(_BitstringList) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param String string to convert to integer
%% @returns an integer value from its string representation
Expand Down Expand Up @@ -973,15 +995,16 @@ binary_to_list(_Binary) ->

%%-----------------------------------------------------------------------------
%% @param Bitstring Bitstring to convert to list
%% @returns a list of bytes from the bitstring
%% @returns a list of bytes, with a final element holding the trailing bits if
%% `Bitstring' is not a whole number of bytes
%% @doc Convert a bitstring to a list of bytes.
%%
%% Unlike Erlang/OTP, AtomVM only supports byte-aligned bitstrings (binaries),
%% so the returned list never has a trailing bitstring and this function
%% behaves like `binary_to_list/1'.
%% If the number of bits in `Bitstring' is not divisible by `8', the
%% last element of the list is a bitstring containing the trailing
%% `1..7' bits, e.g. `bitstring_to_list(<<1:1>>)' returns `[<<1:1>>]'.
%% @end
%%-----------------------------------------------------------------------------
-spec bitstring_to_list(Bitstring :: bitstring()) -> [byte()].
-spec bitstring_to_list(Bitstring :: bitstring()) -> [byte() | bitstring()].
bitstring_to_list(_Bitstring) ->
erlang:nif_error(undefined).

Expand Down Expand Up @@ -2013,6 +2036,19 @@ is_atom(_Term) ->
is_binary(_Term) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Term the term to test
%% @returns `true' if `Term' is a bitstring; `false', otherwise.
%% @doc Return `true' if `Term' is a bitstring (including a binary);
%% `false', otherwise.
%%
%% This function may be used in a guard expression.
%% @end
%%-----------------------------------------------------------------------------
-spec is_bitstring(Term :: term()) -> boolean().
is_bitstring(_Term) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Term the term to test
%% @returns `true' if `Term' is a boolean (`true' or `false'); `false', otherwise.
Expand Down
8 changes: 8 additions & 0 deletions libs/estdlib/src/io_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,14 @@ format_spw(#format{control = w} = Format, L) when is_list(L) ->
[$[, lists:join($,, [format_spw(Format, E) || E <- L]), $]];
format_spw(#format{control = s}, _) ->
error(badarg);
format_spw(_Format, T) when is_bitstring(T) ->
TrailingBits = bit_size(T) rem 8,
WholeBits = bit_size(T) - TrailingBits,
<<Whole:WholeBits/bitstring, Trailing:TrailingBits>> = T,
Numbers =
[erlang:integer_to_list(B) || <<B:8>> <= Whole] ++
[[erlang:integer_to_list(Trailing), $:, erlang:integer_to_list(TrailingBits)]],
[$<, $<, lists:join($,, Numbers), $>, $>];
format_spw(_Format, T) when is_integer(T) ->
erlang:integer_to_list(T);
format_spw(_Format, T) when is_float(T) ->
Expand Down
4 changes: 4 additions & 0 deletions libs/jit/src/default_atoms.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
-define(ALL_ATOM_INDEX, 15).
-define(LOWERCASE_EXIT_ATOM_INDEX, 16).
-define(BADRECORD_ATOM_INDEX, 17).
-define(SYSTEM_LIMIT_ATOM_INDEX, 18).

-define(FALSE_ATOM, ((?FALSE_ATOM_INDEX bsl ?TERM_IMMED2_TAG_SIZE) bor ?TERM_IMMED2_ATOM)).
-define(TRUE_ATOM, ((?TRUE_ATOM_INDEX bsl ?TERM_IMMED2_TAG_SIZE) bor ?TERM_IMMED2_ATOM)).
Expand Down Expand Up @@ -68,6 +69,9 @@
((?LOWERCASE_EXIT_ATOM_INDEX bsl ?TERM_IMMED2_TAG_SIZE) bor ?TERM_IMMED2_ATOM)
).
-define(BADRECORD_ATOM, ((?BADRECORD_ATOM_INDEX bsl ?TERM_IMMED2_TAG_SIZE) bor ?TERM_IMMED2_ATOM)).
-define(SYSTEM_LIMIT_ATOM,
((?SYSTEM_LIMIT_ATOM_INDEX bsl ?TERM_IMMED2_TAG_SIZE) bor ?TERM_IMMED2_ATOM)
).

-define(DEFAULT_ATOMS, #{
false => ?FALSE_ATOM,
Expand Down
Loading
Loading