Skip to content

Add zlib:compress/1 #1570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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 @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for `ets:update_counter/3` and `ets:update_counter/4`.
- Added `erlang:+/1`
- Added `lists:append/1` and `lists:append/2`
- Added `zlib:compress/1`

### Fixed
- ESP32: improved sntp sync speed from a cold boot.
Expand Down
107 changes: 91 additions & 16 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
#include "term.h"
#include "unicode.h"
#include "utils.h"
#ifdef WITH_ZLIB
#include "zlib.h"
#endif

#define MAX_NIF_NAME_LEN 260
#define FLOAT_BUF_SIZE 64
Expand Down Expand Up @@ -186,6 +189,7 @@ static term nif_maps_next(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[]);
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]);

#define DECLARE_MATH_NIF_FUN(moniker) \
static term nif_math_##moniker(Context *ctx, int argc, term argv[]);
Expand Down Expand Up @@ -792,6 +796,12 @@ static const struct Nif erlang_lists_subtract_nif =
.base.type = NIFFunctionType,
.nif_ptr = nif_erlang_lists_subtract
};
static const struct Nif zlib_compress_nif =
{
.base.type = NIFFunctionType,
.nif_ptr = nif_zlib_compress_1
};


#define DEFINE_MATH_NIF(moniker) \
static const struct Nif math_##moniker##_nif = \
Expand Down Expand Up @@ -2411,59 +2421,69 @@ static term nif_erlang_float_to_list(Context *ctx, int argc, term argv[])
return make_list_from_ascii_buf((uint8_t *) float_buf, len, ctx);
}

static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[])
static term list_to_binary(term list, term *ret, Context *ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to keep this approach I really suggest renaming this function to something like: list_to_binary_maybe_gc so the caller will be aware about the fact that after a call to this function, any non trivial term will be invalid.

{
UNUSED(argc);

term t = argv[0];
VALIDATE_VALUE(t, term_is_list);

size_t bin_size;
switch (interop_iolist_size(t, &bin_size)) {
switch (interop_iolist_size(list, &bin_size)) {
case InteropOk:
break;
case InteropMemoryAllocFail:
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
return OUT_OF_MEMORY_ATOM;
case InteropBadArg:
RAISE_ERROR(BADARG_ATOM);
return BADARG_ATOM;
}

char *bin_buf = NULL;
bool buf_allocated = true;
if (bin_size > 0) {
bin_buf = malloc(bin_size);
if (IS_NULL_PTR(bin_buf)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
return OUT_OF_MEMORY_ATOM;
}

switch (interop_write_iolist(t, bin_buf)) {
switch (interop_write_iolist(list, bin_buf)) {
case InteropOk:
break;
case InteropMemoryAllocFail:
free(bin_buf);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
return OUT_OF_MEMORY_ATOM;
case InteropBadArg:
free(bin_buf);
RAISE_ERROR(BADARG_ATOM);
return BADARG_ATOM;
}
} else {
bin_buf = "";
buf_allocated = false;
}

if (UNLIKELY(memory_ensure_free_with_roots(ctx, term_binary_heap_size(bin_size), 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(bin_size)) != MEMORY_GC_OK)) {
if (buf_allocated) {
free(bin_buf);
}
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
return OUT_OF_MEMORY_ATOM;
}
term bin_res = term_from_literal_binary(bin_buf, bin_size, &ctx->heap, ctx->global);

if (buf_allocated) {
free(bin_buf);
}
*ret = bin_res;

return bin_res;
return OK_ATOM;
}

static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[])
{
UNUSED(argc);

term t = argv[0];
VALIDATE_VALUE(t, term_is_list);
term ret;
term result = list_to_binary(t, &ret, ctx);
if (UNLIKELY(result != OK_ATOM)) {
RAISE_ERROR(result);
}
return ret;
}

static avm_int_t to_digit_index(avm_int_t character)
Expand Down Expand Up @@ -5257,6 +5277,61 @@ static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[])
free(cons);
return result;
}

#ifdef WITH_ZLIB
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
{
UNUSED(argc)
term to_compress = argv[0];
if (term_is_list(to_compress)) {
term to_compress_binary;
term result = list_to_binary(to_compress, &to_compress_binary, ctx);
if (result != OK_ATOM) {
RAISE_ERROR(result);
}
to_compress = to_compress_binary;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this approach: we are basically creating a binary (with the same content of the iolist that has been given as argument) on the caller heap that will turn into garbage very soon.
Simple approach: factor out a function that allocates and populates a C buffer.
Cool approach: using iolist folding fun for compressing input piece by piece, without copying data to a temporary buffer.

}
if (UNLIKELY(!term_is_binary(to_compress))) {
RAISE_ERROR(BADARG_ATOM);
}

size_t size = term_binary_size(to_compress);
// to_allocate is an upper bound for compression size
// changes to actual size after calling compress
uLong to_allocate = compressBound(size);
// Bytef is an unsigned char, term_binary_data return const *char
const Bytef *to_compress_data = (const Bytef *) term_binary_data(to_compress);
Bytef *compressed = malloc(to_allocate);
if (IS_NULL_PTR(compressed)) {
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

int z_ret = compress(compressed, &to_allocate, to_compress_data, size);
if (UNLIKELY(z_ret != Z_OK)) {
free(compressed);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

if (UNLIKELY(memory_ensure_free(ctx, term_binary_data_size_in_terms(to_allocate)) != MEMORY_GC_OK)) {
free(compressed);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}
term bin_res = term_from_literal_binary(compressed, to_allocate, &ctx->heap, ctx->global);
free(compressed);
return bin_res;
}
#endif
#ifndef WITH_ZLIB
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
{
UNUSED(argc)
UNUSED(argv)
UNUSED(ctx)
fprintf(stderr, "Error: zlib library needed to use zlib:compress/1\n");
RAISE_ERROR(UNDEFINED_ATOM);
}
#endif

//
// MAINTENANCE NOTE: Exception handling for fp operations using math
// error handling is designed to be thread-safe, as errors are specified
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,4 @@ math:sinh/1, &math_sinh_nif
math:sqrt/1, &math_sqrt_nif
math:tan/1, &math_tan_nif
math:tanh/1, &math_tanh_nif
zlib:compress/1, &zlib_compress_nif
4 changes: 4 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ compile_erlang(test_unicode)
compile_erlang(test_binary_part)
compile_erlang(test_binary_split)

compile_erlang(test_zlib_compress)

compile_erlang(plusone)
compile_erlang(plusone2)
compile_erlang(minusone)
Expand Down Expand Up @@ -762,6 +764,8 @@ add_custom_target(erlang_test_modules DEPENDS
test_binary_part.beam
test_binary_split.beam

test_zlib_compress.beam

plusone.beam
plusone2.beam
minusone.beam
Expand Down
60 changes: 60 additions & 0 deletions tests/erlang_tests/test_zlib_compress.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
%
% This file is part of AtomVM.
%
% Copyright 2024 Tomasz Sobkiewicz <[email protected]>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(test_zlib_compress).

-export([start/0]).

start() ->
ok = test_zlib_compress_binary(),
ok = test_zlib_compress_iolist(),
0.

test_zlib_compress_binary() ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also test errors, such as: zlib:compress([900]) and zlib:compress([{}]).

ToCompress =
<<"Actually, this sentence could be a bit shorter. We definitelly need to compress it.">>,
% Data from original erlang implementation
ProperlyCompressed =
<<120, 156, 13, 203, 203, 13, 128, 32, 16, 4, 208, 86, 166, 0, 67, 15, 86, 225, 153, 207,
24, 54, 65, 48, 236, 112, 176, 123, 185, 191, 119, 102, 173, 216, 218, 119, 64, 213, 28,
206, 46, 246, 76, 228, 177, 90, 65, 34, 34, 146, 9, 94, 199, 20, 103, 192, 69, 20, 222,
214, 77, 220, 11, 157, 44, 208, 216, 252, 121, 39, 221, 97, 10, 63, 241, 84, 30, 23>>,
ProperlyCompressed = zlib:compress(ToCompress),
ok.
test_zlib_compress_iolist() ->
ToCompressList = [
<<"Actually,">>,
" this sentence ",
<<"could be ">>,
[[<<"a ">>, [<<"b">>, <<"i">>]], <<"t">>],
<<" shorter. ">>,
[<<"we ">>],
<<"definitely need ">>,
"to compress ",
<<"it.">>
],
% Data from original erlang implementation
ProperlyCompressed =
<<120, 156, 13, 202, 219, 13, 128, 32, 12, 5, 208, 85, 238, 0, 134, 29, 28, 133, 199, 53,
52, 169, 197, 208, 18, 195, 246, 122, 190, 207, 89, 99, 101, 213, 125, 32, 186, 56, 156,
22, 180, 74, 212, 177, 180, 161, 16, 25, 69, 2, 222, 199, 12, 206, 132, 151, 104, 188,
196, 36, 168, 27, 70, 54, 196, 248, 247, 253, 76, 186, 67, 34, 125, 214, 36, 29, 203>>,
ProperlyCompressed = zlib:compress(ToCompressList),
ok.
2 changes: 2 additions & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ struct Test tests[] = {
TEST_CASE_EXPECTED(test_binary_part, 12),
TEST_CASE(test_binary_split),

TEST_CASE(test_zlib_compress),

TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),

TEST_CASE_EXPECTED(plusone2, 1),
Expand Down
Loading