Skip to content

Commit a9ade6c

Browse files
TheSobkiewiczjgonet
authored andcommitted
nifs: add zlib:compress/1
https://www.erlang.org/doc/apps/erts/zlib.html#compress/1 Signed-off-by: Jakub Gonet <jakub.gonet@swmansion.com>
1 parent fb8b95a commit a9ade6c

File tree

6 files changed

+231
-33
lines changed

6 files changed

+231
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5858
- Removed `externalterm_to_term_copy` added in [0.6.5] and introduced flags to `externalterm_to_term` to perform copy.
5959
- Release images for ESP32 chips are built with ESP-IDF v5.4
6060
- ESP32: SPI peripheral defaults to `"spi2"` instead of deprecated `hspi`
61+
- Added `zlib:compress/1`
6162

6263
### Fixed
6364

src/libAtomVM/nifs.c

Lines changed: 141 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
#include "term_typedef.h"
6161
#include "unicode.h"
6262
#include "utils.h"
63+
#ifdef WITH_ZLIB
64+
#include "zlib.h"
65+
#endif
6366

6467
#define MAX_NIF_NAME_LEN 260
6568
#define FLOAT_BUF_SIZE 64
@@ -198,6 +201,7 @@ static term nif_maps_next(Context *ctx, int argc, term argv[]);
198201
static term nif_unicode_characters_to_list(Context *ctx, int argc, term argv[]);
199202
static term nif_unicode_characters_to_binary(Context *ctx, int argc, term argv[]);
200203
static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[]);
204+
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[]);
201205

202206
#define DECLARE_MATH_NIF_FUN(moniker) \
203207
static term nif_math_##moniker(Context *ctx, int argc, term argv[]);
@@ -858,6 +862,12 @@ static const struct Nif erlang_lists_subtract_nif =
858862
.base.type = NIFFunctionType,
859863
.nif_ptr = nif_erlang_lists_subtract
860864
};
865+
static const struct Nif zlib_compress_nif =
866+
{
867+
.base.type = NIFFunctionType,
868+
.nif_ptr = nif_zlib_compress_1
869+
};
870+
861871

862872
#define DEFINE_MATH_NIF(moniker) \
863873
static const struct Nif math_##moniker##_nif = \
@@ -2520,58 +2530,77 @@ static term nif_erlang_float_to_list(Context *ctx, int argc, term argv[])
25202530
return make_list_from_ascii_buf((uint8_t *) float_buf, len, ctx);
25212531
}
25222532

2523-
static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[])
2533+
static term iolist_to_buffer(term list, char **buf, size_t *size)
25242534
{
2525-
UNUSED(argc);
2526-
2527-
term t = argv[0];
2528-
VALIDATE_VALUE(t, term_is_list);
2535+
*buf = NULL;
2536+
*size = 0;
25292537

25302538
size_t bin_size;
2531-
switch (interop_iolist_size(t, &bin_size)) {
2539+
switch (interop_iolist_size(list, &bin_size)) {
25322540
case InteropOk:
25332541
break;
25342542
case InteropMemoryAllocFail:
2535-
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
2543+
return OUT_OF_MEMORY_ATOM;
25362544
case InteropBadArg:
2537-
RAISE_ERROR(BADARG_ATOM);
2545+
return BADARG_ATOM;
2546+
}
2547+
2548+
if (bin_size == 0) {
2549+
return OK_ATOM;
25382550
}
25392551

25402552
char *bin_buf = NULL;
2541-
bool buf_allocated = true;
2542-
if (bin_size > 0) {
2543-
bin_buf = malloc(bin_size);
2544-
if (IS_NULL_PTR(bin_buf)) {
2545-
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
2546-
}
2553+
bin_buf = malloc(bin_size * sizeof(char));
2554+
if (IS_NULL_PTR(bin_buf)) {
2555+
return OUT_OF_MEMORY_ATOM;
2556+
}
25472557

2548-
switch (interop_write_iolist(t, bin_buf)) {
2549-
case InteropOk:
2550-
break;
2551-
case InteropMemoryAllocFail:
2552-
free(bin_buf);
2553-
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
2554-
case InteropBadArg:
2555-
free(bin_buf);
2556-
RAISE_ERROR(BADARG_ATOM);
2557-
}
2558+
switch (interop_write_iolist(list, bin_buf)) {
2559+
case InteropOk:
2560+
break;
2561+
case InteropMemoryAllocFail:
2562+
free(bin_buf);
2563+
return OUT_OF_MEMORY_ATOM;
2564+
case InteropBadArg:
2565+
free(bin_buf);
2566+
return BADARG_ATOM;
2567+
}
2568+
2569+
*buf = bin_buf;
2570+
*size = bin_size;
2571+
return OK_ATOM;
2572+
}
2573+
2574+
static term nif_erlang_list_to_binary_1(Context *ctx, int argc, term argv[])
2575+
{
2576+
UNUSED(argc);
2577+
2578+
term t = argv[0];
2579+
VALIDATE_VALUE(t, term_is_list);
2580+
2581+
char *bin_buf = NULL;
2582+
char *alloc_ptr = NULL;
2583+
size_t bin_size = 0;
2584+
2585+
term status = iolist_to_buffer(t, &bin_buf, &bin_size);
2586+
if (UNLIKELY(status != OK_ATOM)) {
2587+
RAISE_ERROR(status);
2588+
}
2589+
bool allocated = bin_size > 0;
2590+
if (allocated) {
2591+
alloc_ptr = bin_buf;
25582592
} else {
25592593
bin_buf = "";
2560-
buf_allocated = false;
2594+
bin_size = 0;
25612595
}
25622596

2563-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, term_binary_heap_size(bin_size), 1, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
2564-
if (buf_allocated) {
2565-
free(bin_buf);
2566-
}
2597+
if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(bin_size)) != MEMORY_GC_OK)) {
2598+
free(alloc_ptr);
25672599
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
25682600
}
25692601
term bin_res = term_from_literal_binary(bin_buf, bin_size, &ctx->heap, ctx->global);
25702602

2571-
if (buf_allocated) {
2572-
free(bin_buf);
2573-
}
2574-
2603+
free(alloc_ptr);
25752604
return bin_res;
25762605
}
25772606

@@ -5837,6 +5866,85 @@ static term nif_erlang_lists_subtract(Context *ctx, int argc, term argv[])
58375866
free(cons);
58385867
return result;
58395868
}
5869+
5870+
#ifdef WITH_ZLIB
5871+
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
5872+
{
5873+
UNUSED(argc)
5874+
5875+
term error = OK_ATOM;
5876+
5877+
Bytef *output_buf = NULL;
5878+
char *input_buf = NULL;
5879+
size_t input_size = 0;
5880+
char *alloc_ptr = NULL;
5881+
5882+
term input = argv[0];
5883+
if (LIKELY(term_is_list(input))) {
5884+
term status = iolist_to_buffer(input, &input_buf, &input_size);
5885+
if (UNLIKELY(status != OK_ATOM)) {
5886+
error = status;
5887+
goto cleanup;
5888+
}
5889+
5890+
bool allocated = input_size > 0;
5891+
if (allocated) {
5892+
alloc_ptr = input_buf;
5893+
} else {
5894+
input_buf = "";
5895+
input_size = 0;
5896+
}
5897+
} else if (LIKELY(term_is_binary(input))) {
5898+
input_buf = (char *) term_binary_data(input);
5899+
input_size = term_binary_size(input);
5900+
} else {
5901+
error = BADARG_ATOM;
5902+
goto cleanup;
5903+
}
5904+
5905+
// to_allocate is an upper bound for compression size
5906+
// changes to actual size after calling compress
5907+
uLong to_allocate = compressBound(input_size);
5908+
// TODO: use `to_allocate` binary directly instead of allocating a buffer
5909+
// Currently not possible, there's no way to shrink backing buffer for the binary
5910+
output_buf = malloc(to_allocate);
5911+
if (IS_NULL_PTR(output_buf)) {
5912+
error = OUT_OF_MEMORY_ATOM;
5913+
goto cleanup;
5914+
}
5915+
5916+
int z_ret = compress(output_buf, &to_allocate, (const Bytef *) input_buf, input_size);
5917+
if (UNLIKELY(z_ret != Z_OK)) {
5918+
error = OUT_OF_MEMORY_ATOM;
5919+
goto cleanup;
5920+
}
5921+
5922+
if (UNLIKELY(memory_ensure_free(ctx, term_binary_data_size_in_terms(to_allocate)) != MEMORY_GC_OK)) {
5923+
error = OUT_OF_MEMORY_ATOM;
5924+
goto cleanup;
5925+
}
5926+
term bin_res = term_from_literal_binary(output_buf, to_allocate, &ctx->heap, ctx->global);
5927+
5928+
cleanup:
5929+
free(alloc_ptr);
5930+
free(output_buf);
5931+
if (UNLIKELY(error != OK_ATOM)) {
5932+
RAISE_ERROR(error);
5933+
}
5934+
return bin_res;
5935+
}
5936+
#endif
5937+
#ifndef WITH_ZLIB
5938+
static term nif_zlib_compress_1(Context *ctx, int argc, term argv[])
5939+
{
5940+
UNUSED(argc)
5941+
UNUSED(argv)
5942+
UNUSED(ctx)
5943+
fprintf(stderr, "Error: zlib library needed to use zlib:compress/1\n");
5944+
RAISE_ERROR(UNDEFINED_ATOM);
5945+
}
5946+
#endif
5947+
58405948
//
58415949
// MAINTENANCE NOTE: Exception handling for fp operations using math
58425950
// error handling is designed to be thread-safe, as errors are specified

src/libAtomVM/nifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,4 @@ math:sinh/1, &math_sinh_nif
209209
math:sqrt/1, &math_sqrt_nif
210210
math:tan/1, &math_tan_nif
211211
math:tanh/1, &math_tanh_nif
212+
zlib:compress/1, &zlib_compress_nif

tests/erlang_tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ compile_erlang(test_split_binary)
287287
compile_erlang(test_binary_replace)
288288
compile_erlang(test_binary_match)
289289

290+
compile_erlang(test_zlib_compress)
291+
290292
compile_erlang(plusone)
291293
compile_erlang(plusone2)
292294
compile_erlang(minusone)
@@ -780,6 +782,8 @@ add_custom_target(erlang_test_modules DEPENDS
780782
test_binary_replace.beam
781783
test_binary_match.beam
782784

785+
test_zlib_compress.beam
786+
783787
plusone.beam
784788
plusone2.beam
785789
minusone.beam
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2024 Tomasz Sobkiewicz <tomasz.sobkiewicz@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(test_zlib_compress).
22+
23+
-export([start/0]).
24+
25+
start() ->
26+
ok = compress_binary(),
27+
ok = compress_iolist(),
28+
ok = bad_inputs(),
29+
0.
30+
31+
compress_binary() ->
32+
ToCompress =
33+
<<"Actually, this sentence could be a bit shorter. We definitelly need to compress it.">>,
34+
% Data from original erlang implementation
35+
ProperlyCompressed =
36+
<<120, 156, 13, 203, 203, 13, 128, 32, 16, 4, 208, 86, 166, 0, 67, 15, 86, 225, 153, 207,
37+
24, 54, 65, 48, 236, 112, 176, 123, 185, 191, 119, 102, 173, 216, 218, 119, 64, 213, 28,
38+
206, 46, 246, 76, 228, 177, 90, 65, 34, 34, 146, 9, 94, 199, 20, 103, 192, 69, 20, 222,
39+
214, 77, 220, 11, 157, 44, 208, 216, 252, 121, 39, 221, 97, 10, 63, 241, 84, 30, 23>>,
40+
ProperlyCompressed = zlib:compress(ToCompress),
41+
ok.
42+
43+
compress_iolist() ->
44+
ToCompressList = [
45+
<<"Actually,">>,
46+
" this sentence ",
47+
<<"could be ">>,
48+
[[<<"a ">>, [<<"b">>, <<"i">>]], <<"t">>],
49+
<<" shorter. ">>,
50+
[<<"we ">>],
51+
<<"definitely need ">>,
52+
"to compress ",
53+
<<"it.">>
54+
],
55+
% Data from original erlang implementation
56+
ProperlyCompressed =
57+
<<120, 156, 13, 202, 219, 13, 128, 32, 12, 5, 208, 85, 238, 0, 134, 29, 28, 133, 199, 53,
58+
52, 169, 197, 208, 18, 195, 246, 122, 190, 207, 89, 99, 101, 213, 125, 32, 186, 56, 156,
59+
22, 180, 74, 212, 177, 180, 161, 16, 25, 69, 2, 222, 199, 12, 206, 132, 151, 104, 188,
60+
196, 36, 168, 27, 70, 54, 196, 248, 247, 253, 76, 186, 67, 34, 125, 214, 36, 29, 203>>,
61+
ProperlyCompressed = zlib:compress(ToCompressList),
62+
63+
true = is_binary(zlib:compress([])),
64+
true = is_binary(zlib:compress([[]])),
65+
true = is_binary(zlib:compress([[], []])),
66+
true = is_binary(zlib:compress([[] | <<>>])),
67+
ok.
68+
69+
bad_inputs() ->
70+
ok = raises(badarg, fun() -> zlib:compress(1) end),
71+
ok = raises(badarg, fun() -> zlib:compress([{}]) end),
72+
ok = raises(badarg, fun() -> zlib:compress([1024]) end),
73+
ok.
74+
75+
raises(Error, F) ->
76+
try F() of
77+
V ->
78+
{unexpected, V}
79+
catch
80+
error:Error -> ok;
81+
C:E -> {unexpected, C, E}
82+
end.

tests/test.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ struct Test tests[] = {
328328
TEST_CASE(test_binary_replace),
329329
TEST_CASE(test_binary_match),
330330

331+
TEST_CASE(test_zlib_compress),
332+
331333
TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),
332334

333335
TEST_CASE_EXPECTED(plusone2, 1),

0 commit comments

Comments
 (0)