Skip to content

Commit eecfae9

Browse files
committed
Merge pull request #1570 from jgonet/jgonet/zlib-compress
Add zlib:compress/1 https://www.erlang.org/doc/apps/erts/zlib.html#compress/1 Fixes #1632. 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 fb8b95a + b7745b4 commit eecfae9

File tree

8 files changed

+293
-85
lines changed

8 files changed

+293
-85
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/interop.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "tempstack.h"
2727
#include "term.h"
2828
#include "term_typedef.h"
29+
#include "utils.h"
2930
#include "valueshashtable.h"
3031
#include <stdint.h>
3132

@@ -307,10 +308,19 @@ inline InteropFunctionResult interop_chardata_fold(term t, interop_chardata_fold
307308
return InteropOk;
308309
}
309310

311+
static inline bool is_u8(avm_int_t value)
312+
{
313+
return 0 <= value && value <= UCHAR_MAX;
314+
}
315+
310316
static inline InteropFunctionResult size_fold_fun(term t, void *accum)
311317
{
312318
size_t *size = (size_t *) accum;
313319
if (term_is_integer(t)) {
320+
avm_int_t value = term_to_int(t);
321+
if (UNLIKELY(!is_u8(value))) {
322+
return InteropBadArg;
323+
}
314324
*size += 1;
315325
} else /* term_is_binary(t) */ {
316326
*size += term_binary_size(t);
@@ -328,9 +338,15 @@ static inline InteropFunctionResult write_string_fold_fun(term t, void *accum)
328338
{
329339
char **p = (char **) accum;
330340
if (term_is_integer(t)) {
331-
**p = term_to_int(t);
341+
avm_int_t value = term_to_int(t);
342+
if (UNLIKELY(!is_u8(value))) {
343+
return InteropBadArg;
344+
}
345+
**p = value;
332346
(*p)++;
333-
} else /* term_is_binary(t) */ {
347+
} else {
348+
assert(term_is_binary(t));
349+
334350
int len = term_binary_size(t);
335351
memcpy(*p, term_binary_data(t), len);
336352
*p += len;

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

0 commit comments

Comments
 (0)