Skip to content

Commit

Permalink
Merge pull request #811 from pguyot/w37/unaligned-strings
Browse files Browse the repository at this point in the history
Add support for unaligned STRINGS in binaries

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
  • Loading branch information
bettio committed Oct 6, 2023
2 parents 9e4c220 + f462475 commit a2bdaa7
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `crypto:crypto_one_time/4,5` on ESP32
- Improved nif and port support on STM32
- Added support for `atomvm:posix_clock_settime/2`
- Added support for creations of binaries with unaligned strings

### Fixed

Expand Down
39 changes: 39 additions & 0 deletions src/libAtomVM/bitstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,42 @@ bool bitstring_utf32_decode(const uint8_t *buf, size_t len, int32_t *c, enum Bit
return true;
}
}

void bitstring_copy_bits_incomplete_bytes(uint8_t *dst, size_t bits_offset, const uint8_t *src, size_t bits_count)
{
size_t byte_offset = bits_offset / 8;
size_t bit_offset = bits_offset - (8 * byte_offset);
if (bits_offset % 8 == 0 && bits_count >= 8) {
size_t bytes_count = bits_count / 8;
memcpy(dst + byte_offset, src, bytes_count);
src += bytes_count;
byte_offset += bytes_count;
bits_count -= bytes_count * 8;
}
// Eventually copy bit by bit
dst += byte_offset;
uint8_t dest_byte = *dst;
uint8_t src_byte = *src++;
int dest_bit_ix = 7 - bit_offset;
int src_bit_ix = 7;
while (bits_count > 0) {
if (src_byte & (1 << src_bit_ix)) {
dest_byte |= 1 << dest_bit_ix;
} else {
dest_byte &= ~(1 << dest_bit_ix);
}
if (dest_bit_ix == 0) {
*dst++ = dest_byte;
dest_byte = *dst;
dest_bit_ix = 8;
}
if (src_bit_ix == 0) {
src_byte = *src++;
src_bit_ix = 8;
}
dest_bit_ix--;
src_bit_ix--;
bits_count--;
}
*dst = dest_byte;
}
28 changes: 28 additions & 0 deletions src/libAtomVM/bitstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,34 @@ static inline bool bitstring_match_utf32(term src_bin, size_t offset, int32_t *c
return bitstring_utf32_decode(src, term_binary_size(src_bin) - byte_offset, c, bs_flags);
}

/**
* @brief Copy bits_count bits from src to dst[bits_offset..]
* @details Called by bitstring_copy_bits when bytes are not complete.
*
* @param dst destination buffer
* @param bits_offset offset in bits in destination buffer
* @param src source buffer
* @param bits_count number of bits
*/
void bitstring_copy_bits_incomplete_bytes(uint8_t *dst, size_t bits_offset, const uint8_t *src, size_t bits_count);

/**
* @brief Copy bits_count bits from src to dst[bits_offset..]
*
* @param dst destination buffer
* @param bits_offset offset in bits in destination buffer
* @param src source buffer
* @param bits_count number of bits
*/
static inline void bitstring_copy_bits(uint8_t *dst, size_t bits_offset, const uint8_t *src, size_t bits_count)
{
if (bits_offset % 8 == 0 && bits_count % 8 == 0) {
memcpy(dst + (bits_offset / 8), src, bits_count / 8);
} else {
bitstring_copy_bits_incomplete_bytes(dst, bits_offset, src, bits_count);
}
}

#ifdef __cplusplus
}
#endif
Expand Down
18 changes: 6 additions & 12 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -4646,10 +4646,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
TRACE("bs_put_string: Bad state. ctx->bs is not a binary.\n");
RAISE_ERROR(BADARG_ATOM);
}
if (ctx->bs_offset % 8 != 0) {
TRACE("bs_put_string: Unsupported bit syntax operation. Writing strings must be byte-aligend.\n");
RAISE_ERROR(UNSUPPORTED_ATOM);
}

TRACE("bs_put_string/2, size=%u offset=%u\n", size, offset);

Expand All @@ -4660,8 +4656,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
RAISE_ERROR(BADARG_ATOM);
}

memcpy((char *) term_binary_data(ctx->bs) + ctx->bs_offset / 8, str, size);
ctx->bs_offset += 8 * size;
size_t size_in_bits = size * 8;
uint8_t *dst = (uint8_t *) term_binary_data(ctx->bs);
bitstring_copy_bits(dst, ctx->bs_offset, str, size_in_bits);
ctx->bs_offset += size_in_bits;
#endif
NEXT_INSTRUCTION(next_off);
break;
Expand Down Expand Up @@ -6798,15 +6796,11 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
break;
}
case STRING_ATOM: {
if (offset % 8) {
TRACE("bs_create_bin/6: current offset (%li) is not evenly divisible by 8\n", offset);
RAISE_ERROR(UNSUPPORTED_ATOM);
}
uint8_t *dst = (uint8_t *) term_binary_data(t) + (offset / 8);
uint8_t *dst = (uint8_t *) term_binary_data(t);
size_t remaining = 0;
const uint8_t *str = module_get_str(mod, src_value, &remaining);
segment_size = size_value * segment_unit;
memcpy(dst, str, segment_size / 8);
bitstring_copy_bits(dst, offset, str, segment_size);
break;
}
case APPEND_ATOM:
Expand Down
14 changes: 14 additions & 0 deletions tests/erlang_tests/test_bs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ start() ->

ok = test_large(),

ok = test_copy_bits_string(),

0.

test_pack_small_ints({A, B, C}, Expect) ->
Expand Down Expand Up @@ -368,4 +370,16 @@ test_large() ->
true = id(X) =:= <<42:1024>>,
ok.

% From OTP22, the sequence 1:1,11:4,3:3 is converted to a string of 1 byte
% OTP 25-26 use OP_BS_CREATE_BIN with STRING
% OTP 22-24 use OP_BS_PUT_STRING
% OTP 21 uses OP_BS_PUT_INTEGER
test_copy_bits_string() ->
A = id(42),
X1 = id(0),
X2 = id(0),
Y1 = <<A:16/little, X1:7, 1:1, 11:4, 3:3, X2:1>>,
<<42, 0, 1, 182>> = Y1,
ok.

id(X) -> X.

0 comments on commit a2bdaa7

Please sign in to comment.