blob: 22fc0eba35fd4b3bc09be0a1833bee453df290b1 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/base/memory.h"
#include <stdint.h>
#include "sw/device/lib/base/macros.h"
#ifdef OT_PLATFORM_RV32
#define OT_PREFIX_IF_NOT_RV32(name) name
#else
#define OT_PREFIX_IF_NOT_RV32(name) ot_##name
#endif
static size_t compute_num_leading_bytes(const void *left, const void *right,
size_t len) {
if (len < alignof(uint32_t)) {
return len;
}
const size_t left_ahead = misalignment32_of((uintptr_t)left);
const size_t right_ahead = misalignment32_of((uintptr_t)right);
if (right == NULL || left_ahead == right_ahead) {
return (4 - left_ahead) & 0x3;
}
return len;
}
/**
* Compute the bounds of the word-aligned region for the given buffers.
*
* It's more efficient for our memory functions to operate on `uint32_t` values
* than individual bytes, but we can only read `uint32_t` values from aligned
* addresses. This function effectively breaks the given buffers into three
* consecutive chunks: the unaligned "head", the aligned "body", and the
* unaligned "tail".
*
* @param[in] left The memory function's first buffer argument. Cannot be NULL.
* @param[in] right The memory function's second buffer argument. May be NULL.
* @param[in] len The length in bytes of both `left` and `right.`
* @param[out] out_body_offset The start of the body region.
* @param[out] out_tail_offset The start of the tail region.
*/
static void compute_alignment(const void *left, const void *right, size_t len,
size_t *out_body_offset,
size_t *out_tail_offset) {
const size_t num_leading_bytes = compute_num_leading_bytes(left, right, len);
*out_body_offset = num_leading_bytes;
const size_t num_words = (len - num_leading_bytes) / sizeof(uint32_t);
*out_tail_offset = num_leading_bytes + num_words * sizeof(uint32_t);
}
static uint32_t repeat_byte_to_u32(uint8_t byte) {
return byte << 24 | byte << 16 | byte << 8 | byte;
}
void *OT_PREFIX_IF_NOT_RV32(memcpy)(void *restrict dest,
const void *restrict src, size_t len) {
if (dest == NULL || src == NULL) {
return dest;
}
unsigned char *dest8 = (unsigned char *)dest;
const unsigned char *src8 = (const unsigned char *)src;
size_t body_offset, tail_offset;
compute_alignment(dest, src, len, &body_offset, &tail_offset);
size_t i = 0;
for (; i < body_offset; ++i) {
dest8[i] = src8[i];
}
for (; i < tail_offset; i += sizeof(uint32_t)) {
uint32_t word = read_32(&src8[i]);
write_32(word, &dest8[i]);
}
for (; i < len; ++i) {
dest8[i] = src8[i];
}
return dest;
}
void *OT_PREFIX_IF_NOT_RV32(memset)(void *dest, int value, size_t len) {
unsigned char *dest8 = (unsigned char *)dest;
const uint8_t value8 = (uint8_t)value;
size_t body_offset, tail_offset;
compute_alignment(dest, NULL, len, &body_offset, &tail_offset);
size_t i = 0;
for (; i < body_offset; ++i) {
dest8[i] = value8;
}
const uint32_t value32 = repeat_byte_to_u32(value8);
for (; i < tail_offset; i += sizeof(uint32_t)) {
write_32(value32, &dest8[i]);
}
for (; i < len; ++i) {
dest8[i] = value8;
}
return dest;
}
enum {
kMemCmpEq = 0,
kMemCmpLt = -42,
kMemCmpGt = 42,
};
int OT_PREFIX_IF_NOT_RV32(memcmp)(const void *lhs, const void *rhs,
size_t len) {
const unsigned char *lhs8 = (const unsigned char *)lhs;
const unsigned char *rhs8 = (const unsigned char *)rhs;
size_t body_offset, tail_offset;
compute_alignment(lhs, rhs, len, &body_offset, &tail_offset);
size_t i = 0;
for (; i < body_offset; ++i) {
if (lhs8[i] < rhs8[i]) {
return kMemCmpLt;
} else if (lhs8[i] > rhs8[i]) {
return kMemCmpGt;
}
}
for (; i < tail_offset; i += sizeof(uint32_t)) {
uint32_t word_left = __builtin_bswap32(read_32(&lhs8[i]));
uint32_t word_right = __builtin_bswap32(read_32(&rhs8[i]));
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"memcmp assumes that the system is little endian.");
if (word_left < word_right) {
return kMemCmpLt;
} else if (word_left > word_right) {
return kMemCmpGt;
}
}
for (; i < len; ++i) {
if (lhs8[i] < rhs8[i]) {
return kMemCmpLt;
} else if (lhs8[i] > rhs8[i]) {
return kMemCmpGt;
}
}
return kMemCmpEq;
}
int memrcmp(const void *lhs, const void *rhs, size_t len) {
const unsigned char *lhs8 = (const unsigned char *)lhs;
const unsigned char *rhs8 = (const unsigned char *)rhs;
size_t body_offset, tail_offset;
compute_alignment(lhs, rhs, len, &body_offset, &tail_offset);
size_t end = len;
for (; end > tail_offset; --end) {
const size_t i = end - 1;
if (lhs8[i] < rhs8[i]) {
return kMemCmpLt;
} else if (lhs8[i] > rhs8[i]) {
return kMemCmpGt;
}
}
for (; end > body_offset; end -= sizeof(uint32_t)) {
const size_t i = end - sizeof(uint32_t);
uint32_t word_left = read_32(&lhs8[i]);
uint32_t word_right = read_32(&rhs8[i]);
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"memrcmp assumes that the system is little endian.");
if (word_left < word_right) {
return kMemCmpLt;
} else if (word_left > word_right) {
return kMemCmpGt;
}
}
for (; end > 0; --end) {
const size_t i = end - 1;
if (lhs8[i] < rhs8[i]) {
return kMemCmpLt;
} else if (lhs8[i] > rhs8[i]) {
return kMemCmpGt;
}
}
return kMemCmpEq;
}
void *OT_PREFIX_IF_NOT_RV32(memchr)(const void *ptr, int value, size_t len) {
const unsigned char *ptr8 = (const unsigned char *)ptr;
const uint8_t value8 = (uint8_t)value;
size_t body_offset, tail_offset;
compute_alignment(ptr, NULL, len, &body_offset, &tail_offset);
size_t i = 0;
for (; i < body_offset; ++i) {
if (ptr8[i] == value8) {
return (void *)&ptr8[i];
}
}
const uint32_t value32 = repeat_byte_to_u32(value8);
for (; i < tail_offset; i += sizeof(uint32_t)) {
uint32_t word = read_32(&ptr8[i]);
uint32_t bits_eq = ~(word ^ value32);
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"memchr assumes that the system is little endian.");
if ((bits_eq & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i];
}
if (((bits_eq >> 8) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 1];
}
if (((bits_eq >> 16) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 2];
}
if (((bits_eq >> 24) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 3];
}
}
for (; i < len; ++i) {
if (ptr8[i] == value8) {
return (void *)&ptr8[i];
}
}
return NULL;
}
void *OT_PREFIX_IF_NOT_RV32(memrchr)(const void *ptr, int value, size_t len) {
const unsigned char *ptr8 = (const unsigned char *)ptr;
const uint8_t value8 = (uint8_t)value;
size_t body_offset, tail_offset;
compute_alignment(ptr, NULL, len, &body_offset, &tail_offset);
size_t end = len;
for (; end > tail_offset; --end) {
const size_t i = end - 1;
if (ptr8[i] == value8) {
return (void *)&ptr8[i];
}
}
const uint32_t value32 = repeat_byte_to_u32(value8);
for (; end > body_offset; end -= sizeof(uint32_t)) {
const size_t i = end - sizeof(uint32_t);
uint32_t word = read_32(&ptr8[i]);
uint32_t bits_eq = ~(word ^ value32);
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"memrchr assumes that the system is little endian.");
if (((bits_eq >> 24) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 3];
}
if (((bits_eq >> 16) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 2];
}
if (((bits_eq >> 8) & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i + 1];
}
if ((bits_eq & UINT8_MAX) == UINT8_MAX) {
return (void *)&ptr8[i];
}
}
for (; end > 0; --end) {
const size_t i = end - 1;
if (ptr8[i] == value8) {
return (void *)&ptr8[i];
}
}
return NULL;
}
// `extern` declarations to give the inline functions in the corresponding
// header a link location.
extern ptrdiff_t misalignment32_of(uintptr_t);
extern uint32_t read_32(const void *);
extern void write_32(uint32_t, void *);
extern uint64_t read_64(const void *);
extern void write_64(uint64_t, void *);
#undef OT_PREFIX_IF_NOT_RV32