452 lines
11 KiB
C++
452 lines
11 KiB
C++
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
//
|
|
// 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.
|
|
|
|
#include <IACore/DataOps.hpp>
|
|
#include <IACore/Platform.hpp>
|
|
|
|
#include <bit>
|
|
#include <cstring>
|
|
#include <zlib.h>
|
|
#include <zstd.h>
|
|
|
|
#if IA_ARCH_X64
|
|
#include <immintrin.h>
|
|
#endif
|
|
|
|
#if IA_ARCH_ARM64
|
|
#include <arm_acle.h>
|
|
#endif
|
|
|
|
namespace IACore {
|
|
template <typename T>
|
|
[[nodiscard]] inline auto read_unaligned(Const<Const<u8> *> ptr) -> T {
|
|
Mut<T> v;
|
|
std::memcpy(&v, ptr, sizeof(T));
|
|
return v;
|
|
}
|
|
|
|
struct Crc32Tables {
|
|
Mut<u32> table[8][256] = {};
|
|
|
|
consteval Crc32Tables() {
|
|
constexpr Const<u32> T = 0x82F63B78;
|
|
|
|
for (Mut<u32> i = 0; i < 256; i++) {
|
|
Mut<u32> crc = i;
|
|
for (Mut<i32> j = 0; j < 8; j++) {
|
|
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
|
|
}
|
|
table[0][i] = crc;
|
|
}
|
|
|
|
for (Mut<i32> i = 0; i < 256; i++) {
|
|
for (Mut<i32> slice = 1; slice < 8; slice++) {
|
|
Const<u32> prev = table[slice - 1][i];
|
|
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
static constexpr Const<Crc32Tables> CRC32_TABLES{};
|
|
|
|
#if IA_ARCH_X64
|
|
inline auto crc32_x64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
|
Mut<const u8 *> p = data.data();
|
|
|
|
Mut<u32> crc = 0xFFFFFFFF;
|
|
Mut<usize> len = data.size();
|
|
|
|
while (len >= 8) {
|
|
Const<u64> chunk = read_unaligned<u64>(p);
|
|
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
|
|
p += 8;
|
|
len -= 8;
|
|
}
|
|
|
|
while (len--) {
|
|
crc = _mm_crc32_u8(crc, *p++);
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
#endif
|
|
|
|
#if IA_ARCH_ARM64
|
|
__attribute__((target("+crc"))) inline auto
|
|
crc32_arm64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
|
Mut<const u8 *> p = data.data();
|
|
|
|
Mut<u32> crc = 0xFFFFFFFF;
|
|
Mut<usize> len = data.size();
|
|
|
|
while (len >= 8) {
|
|
Const<u64> chunk = read_unaligned<u64>(p);
|
|
crc = __crc32cd(crc, chunk);
|
|
p += 8;
|
|
len -= 8;
|
|
}
|
|
|
|
while (len--) {
|
|
crc = __crc32cb(crc, *p++);
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
#endif
|
|
|
|
inline auto crc32_software_slice8(Ref<Span<Const<u8>>> data) -> u32 {
|
|
Mut<const u8 *> p = data.data();
|
|
Mut<u32> crc = 0xFFFFFFFF;
|
|
Mut<usize> len = data.size();
|
|
|
|
while (len >= 8) {
|
|
Const<u32> term1 = crc ^ read_unaligned<u32>(p);
|
|
Const<u32> term2 = read_unaligned<u32>(p + 4);
|
|
|
|
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
|
|
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
|
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^
|
|
CRC32_TABLES.table[4][(term1 >> 24)] ^
|
|
CRC32_TABLES.table[3][term2 & 0xFF] ^
|
|
CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
|
|
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^
|
|
CRC32_TABLES.table[0][(term2 >> 24)];
|
|
|
|
p += 8;
|
|
len -= 8;
|
|
}
|
|
|
|
while (len--) {
|
|
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
|
|
auto DataOps::crc32(Ref<Span<Const<u8>>> data) -> u32 {
|
|
#if IA_ARCH_X64
|
|
// IACore mandates AVX2 so no need to check
|
|
return crc32_x64_hw(data);
|
|
#elif IA_ARCH_ARM64
|
|
if (Platform::GetCapabilities().HardwareCRC32) {
|
|
return crc32_arm64_hw(data);
|
|
}
|
|
#endif
|
|
return crc32_software_slice8(data);
|
|
}
|
|
|
|
constexpr Const<u32> XXH_PRIME32_1 = 0x9E3779B1U;
|
|
constexpr Const<u32> XXH_PRIME32_2 = 0x85EBCA77U;
|
|
constexpr Const<u32> XXH_PRIME32_3 = 0xC2B2AE3DU;
|
|
constexpr Const<u32> XXH_PRIME32_4 = 0x27D4EB2FU;
|
|
constexpr Const<u32> XXH_PRIME32_5 = 0x165667B1U;
|
|
|
|
inline auto xxh32_round(Mut<u32> seed, Const<u32> input) -> u32 {
|
|
seed += input * XXH_PRIME32_2;
|
|
seed = std::rotl(seed, 13);
|
|
seed *= XXH_PRIME32_1;
|
|
return seed;
|
|
}
|
|
|
|
auto DataOps::hash_xxhash(Ref<String> string, Const<u32> seed) -> u32 {
|
|
return hash_xxhash(
|
|
Span<Const<u8>>(reinterpret_cast<const u8 *>(string.data()),
|
|
string.length()),
|
|
seed);
|
|
}
|
|
|
|
auto DataOps::hash_xxhash(Ref<Span<Const<u8>>> data, Const<u32> seed) -> u32 {
|
|
Mut<const u8 *> p = data.data();
|
|
Const<const u8 *> b_end = p + data.size();
|
|
Mut<u32> h32{};
|
|
|
|
if (data.size() >= 16) {
|
|
Const<const u8 *> limit = b_end - 16;
|
|
|
|
Mut<u32> v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
|
Mut<u32> v2 = seed + XXH_PRIME32_2;
|
|
Mut<u32> v3 = seed + 0;
|
|
Mut<u32> v4 = seed - XXH_PRIME32_1;
|
|
|
|
do {
|
|
v1 = xxh32_round(v1, read_unaligned<u32>(p));
|
|
p += 4;
|
|
v2 = xxh32_round(v2, read_unaligned<u32>(p));
|
|
p += 4;
|
|
v3 = xxh32_round(v3, read_unaligned<u32>(p));
|
|
p += 4;
|
|
v4 = xxh32_round(v4, read_unaligned<u32>(p));
|
|
p += 4;
|
|
} while (p <= limit);
|
|
|
|
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) +
|
|
std::rotl(v4, 18);
|
|
} else {
|
|
h32 = seed + XXH_PRIME32_5;
|
|
}
|
|
|
|
h32 += static_cast<u32>(data.size());
|
|
|
|
while (p + 4 <= b_end) {
|
|
Const<u32> t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
|
h32 += t;
|
|
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
|
p += 4;
|
|
}
|
|
|
|
while (p < b_end) {
|
|
h32 += (*p++) * XXH_PRIME32_5;
|
|
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
|
}
|
|
|
|
h32 ^= h32 >> 15;
|
|
h32 *= XXH_PRIME32_2;
|
|
h32 ^= h32 >> 13;
|
|
h32 *= XXH_PRIME32_3;
|
|
h32 ^= h32 >> 16;
|
|
|
|
return h32;
|
|
}
|
|
|
|
constexpr Const<u32> FNV1A_32_PRIME = 0x01000193;
|
|
constexpr Const<u32> FNV1A_32_OFFSET = 0x811c9dc5;
|
|
|
|
auto DataOps::hash_fnv1a(Ref<String> string) -> u32 {
|
|
Mut<u32> hash = FNV1A_32_OFFSET;
|
|
for (Const<char> c : string) {
|
|
hash ^= static_cast<u8>(c);
|
|
hash *= FNV1A_32_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
auto DataOps::hash_fnv1a(Ref<Span<Const<u8>>> data) -> u32 {
|
|
Mut<u32> hash = FNV1A_32_OFFSET;
|
|
Const<const u8 *> ptr = data.data();
|
|
|
|
for (Mut<usize> i = 0; i < data.size(); ++i) {
|
|
hash ^= ptr[i];
|
|
hash *= FNV1A_32_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
auto DataOps::detect_compression(Const<Span<Const<u8>>> data)
|
|
-> CompressionType {
|
|
if (data.size() < 2) {
|
|
return CompressionType::None;
|
|
}
|
|
|
|
if (data[0] == 0x1F && data[1] == 0x8B) {
|
|
return CompressionType::Gzip;
|
|
}
|
|
|
|
if (data[0] == 0x78 &&
|
|
(data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) {
|
|
return CompressionType::Zlib;
|
|
}
|
|
|
|
return CompressionType::None;
|
|
}
|
|
|
|
auto DataOps::zlib_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
Mut<z_stream> zs{};
|
|
zs.zalloc = Z_NULL;
|
|
zs.zfree = Z_NULL;
|
|
zs.opaque = Z_NULL;
|
|
|
|
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
|
return fail("Failed to initialize zlib inflate");
|
|
}
|
|
|
|
zs.next_in = const_cast<Bytef *>(data.data());
|
|
zs.avail_in = static_cast<uInt>(data.size());
|
|
|
|
Mut<Vec<u8>> out_buffer;
|
|
Const<usize> guess_size =
|
|
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
|
out_buffer.resize(guess_size);
|
|
|
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
|
|
|
Mut<int> ret;
|
|
do {
|
|
if (zs.avail_out == 0) {
|
|
Const<usize> current_pos = zs.total_out;
|
|
Const<usize> new_size = out_buffer.size() * 2;
|
|
out_buffer.resize(new_size);
|
|
|
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
|
|
zs.avail_out = static_cast<uInt>(new_size - current_pos);
|
|
}
|
|
|
|
ret = inflate(&zs, Z_NO_FLUSH);
|
|
|
|
} while (ret == Z_OK);
|
|
|
|
inflateEnd(&zs);
|
|
|
|
if (ret != Z_STREAM_END) {
|
|
return fail("Failed to inflate: corrupt data or stream error");
|
|
}
|
|
|
|
out_buffer.resize(zs.total_out);
|
|
|
|
return out_buffer;
|
|
}
|
|
|
|
auto DataOps::zlib_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
Mut<z_stream> zs{};
|
|
zs.zalloc = Z_NULL;
|
|
zs.zfree = Z_NULL;
|
|
zs.opaque = Z_NULL;
|
|
|
|
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
|
return fail("Failed to initialize zlib deflate");
|
|
}
|
|
|
|
zs.next_in = const_cast<Bytef *>(data.data());
|
|
zs.avail_in = static_cast<uInt>(data.size());
|
|
|
|
Mut<Vec<u8>> out_buffer;
|
|
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
|
|
|
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
|
|
|
Const<int> ret = deflate(&zs, Z_FINISH);
|
|
|
|
if (ret != Z_STREAM_END) {
|
|
deflateEnd(&zs);
|
|
return fail("Failed to deflate, ran out of buffer memory");
|
|
}
|
|
|
|
out_buffer.resize(zs.total_out);
|
|
|
|
deflateEnd(&zs);
|
|
return out_buffer;
|
|
}
|
|
|
|
auto DataOps::zstd_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
Const<unsigned long long> content_size =
|
|
ZSTD_getFrameContentSize(data.data(), data.size());
|
|
|
|
if (content_size == ZSTD_CONTENTSIZE_ERROR) {
|
|
return fail("Failed to inflate: Not valid ZSTD compressed data");
|
|
}
|
|
|
|
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
|
|
Mut<Vec<u8>> out_buffer;
|
|
out_buffer.resize(static_cast<usize>(content_size));
|
|
|
|
Const<usize> d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
|
data.data(), data.size());
|
|
|
|
if (ZSTD_isError(d_size)) {
|
|
return fail("Failed to inflate: {}", ZSTD_getErrorName(d_size));
|
|
}
|
|
|
|
return out_buffer;
|
|
}
|
|
|
|
Mut<ZSTD_DCtx *> dctx = ZSTD_createDCtx();
|
|
Mut<Vec<u8>> out_buffer;
|
|
out_buffer.resize(data.size() * 2);
|
|
|
|
Mut<ZSTD_inBuffer> input = {data.data(), data.size(), 0};
|
|
Mut<ZSTD_outBuffer> output = {out_buffer.data(), out_buffer.size(), 0};
|
|
|
|
Mut<usize> ret;
|
|
do {
|
|
ret = ZSTD_decompressStream(dctx, &output, &input);
|
|
|
|
if (ZSTD_isError(ret)) {
|
|
ZSTD_freeDCtx(dctx);
|
|
return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
|
|
}
|
|
|
|
if (output.pos == output.size) {
|
|
Const<usize> new_size = out_buffer.size() * 2;
|
|
out_buffer.resize(new_size);
|
|
output.dst = out_buffer.data();
|
|
output.size = new_size;
|
|
}
|
|
|
|
} while (ret != 0);
|
|
|
|
out_buffer.resize(output.pos);
|
|
ZSTD_freeDCtx(dctx);
|
|
|
|
return out_buffer;
|
|
}
|
|
|
|
auto DataOps::zstd_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
Const<usize> max_dst_size = ZSTD_compressBound(data.size());
|
|
|
|
Mut<Vec<u8>> out_buffer;
|
|
out_buffer.resize(max_dst_size);
|
|
|
|
Const<usize> compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
|
|
data.data(), data.size(), 3);
|
|
|
|
if (ZSTD_isError(compressed_size)) {
|
|
return fail("Failed to deflate: {}", ZSTD_getErrorName(compressed_size));
|
|
}
|
|
|
|
out_buffer.resize(compressed_size);
|
|
return out_buffer;
|
|
}
|
|
|
|
auto DataOps::gzip_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
Mut<z_stream> zs{};
|
|
zs.zalloc = Z_NULL;
|
|
zs.zfree = Z_NULL;
|
|
zs.opaque = Z_NULL;
|
|
|
|
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
|
Z_DEFAULT_STRATEGY) != Z_OK) {
|
|
return fail("Failed to initialize gzip deflate");
|
|
}
|
|
|
|
zs.next_in = const_cast<Bytef *>(data.data());
|
|
zs.avail_in = static_cast<uInt>(data.size());
|
|
|
|
Mut<Vec<u8>> out_buffer;
|
|
|
|
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) + 1024);
|
|
|
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
|
|
|
Const<int> ret = deflate(&zs, Z_FINISH);
|
|
|
|
if (ret != Z_STREAM_END) {
|
|
deflateEnd(&zs);
|
|
return fail("Failed to deflate");
|
|
}
|
|
|
|
out_buffer.resize(zs.total_out);
|
|
|
|
deflateEnd(&zs);
|
|
return out_buffer;
|
|
}
|
|
|
|
auto DataOps::gzip_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
|
return zlib_inflate(data);
|
|
}
|
|
|
|
} // namespace IACore
|