-
Notifications
You must be signed in to change notification settings - Fork 114
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
base: main
Are you sure you want to change the base?
Add zlib:compress/1 #1570
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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[]); | ||
|
@@ -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 = \ | ||
|
@@ -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) | ||
{ | ||
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) | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
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 | ||
|
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() -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also test errors, such as: |
||
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. |
There was a problem hiding this comment.
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.