diff --git a/builtins/flzma2/CMakeLists.txt b/builtins/flzma2/CMakeLists.txt new file mode 100644 index 0000000000000..6f8e814801d03 --- /dev/null +++ b/builtins/flzma2/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +project(FLMA2 C) + +file(GLOB FLMA2_HEADERS *.h) +file(GLOB FLMA2_SOURCES *.c) + +unset(FLMA2_FOUND CACHE) +unset(FLMA2_FOUND PARENT_SCOPE) +set(FLMA2_FOUND TRUE CACHE BOOL "" FORCE) + +file(STRINGS "fast-lzma2.h" FLMA2_H REGEX "^#define FL2_VERSION_[A-Z]+[ ]+[0-9]+.*$") +string(REGEX REPLACE ".+FLMA2_VERSION_MAJOR[ ]+([0-9]+).*$" "\\1" FL2_VERSION_MAJOR "${FLMA2_H}") +string(REGEX REPLACE ".+FLMA2_VERSION_MINOR[ ]+([0-9]+).*$" "\\1" FL2_VERSION_MINOR "${FLMA2_H}") +string(REGEX REPLACE ".+FLMA2_VERSION_RELEASE[ ]+([0-9]+).*$" "\\1" FL2_VERSION_PATCH "${FLMA2_H}") +set(FLMA2_VERSION_STRING "${FLMA2_VERSION_MAJOR}.${FLMA2_VERSION_MINOR}.${FLMA2_VERSION_PATCH}") +unset(FLMA2_H) + +set(FLMA2_VERSION ${FLMA2_VERSION_STRING} CACHE INTERNAL "" FORCE) +set(FLMA2_VERSION_STRING ${FLMA2_VERSION_STRING} CACHE INTERNAL "" FORCE) + +set(FLMA2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" FORCE) +set(FLMA2_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" FORCE) + +add_library(fast-lzma2 STATIC ${FLMA2_HEADERS} ${FLMA2_SOURCES}) +if(NOT MSVC) + target_compile_options(fast-lzma2 PRIVATE -fPIC) +endif() +target_link_libraries(fast-lzma2 PRIVATE xxHash::xxHash) + +add_library(FLMA2::FLMA2 ALIAS fast-lzma2) + +set(FLMA2_LIBRARY $ CACHE INTERNAL "") +set(FLMA2_LIBRARIES FLMA2::FLMA2 CACHE INTERNAL "") +set_property(GLOBAL APPEND PROPERTY ROOT_BUILTIN_TARGETS FLMA2::FLMA2) + +ROOT_INSTALL_HEADERS() diff --git a/builtins/flzma2/atomic.h b/builtins/flzma2/atomic.h new file mode 100644 index 0000000000000..aa4d977847180 --- /dev/null +++ b/builtins/flzma2/atomic.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Conor McCarthy + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * + */ + +#ifndef FL2_ATOMIC_H +#define FL2_ATOMIC_H + +#if defined (__cplusplus) +extern "C" { +#endif + +/* atomic add */ + +#if !defined(FL2_SINGLETHREAD) && defined(_WIN32) + +#ifdef WINVER +#undef WINVER +#endif +#define WINVER 0x0600 + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + + +typedef LONG volatile FL2_atomic; +#define ATOMIC_INITIAL_VALUE -1 +#define FL2_atomic_increment(n) InterlockedIncrement(&n) +#define FL2_atomic_add(n, a) InterlockedAdd(&n, a) +#define FL2_nonAtomic_increment(n) (++n) + +#elif !defined(FL2_SINGLETHREAD) && defined(__GNUC__) + +typedef long FL2_atomic; +#define ATOMIC_INITIAL_VALUE 0 +#define FL2_atomic_increment(n) __sync_fetch_and_add(&n, 1) +#define FL2_atomic_add(n, a) __sync_fetch_and_add(&n, a) +#define FL2_nonAtomic_increment(n) (n++) + +#elif !defined(FL2_SINGLETHREAD) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) /* C11 */ + +#include + +typedef _Atomic long FL2_atomic; +#define ATOMIC_INITIAL_VALUE 0 +#define FL2_atomic_increment(n) atomic_fetch_add(&n, 1) +#define FL2_atomic_add(n, a) atomic_fetch_add(&n, a) +#define FL2_nonAtomic_increment(n) (n++) + +#else /* No atomics */ + +# ifndef FL2_SINGLETHREAD +# error No atomic operations available. Change compiler config or define FL2_SINGLETHREAD for the entire build. +# endif + +typedef long FL2_atomic; +#define ATOMIC_INITIAL_VALUE 0 +#define FL2_atomic_increment(n) (n++) +#define FL2_atomic_add(n, a) (n += (a)) +#define FL2_nonAtomic_increment(n) (n++) + +#endif /* FL2_SINGLETHREAD */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* FL2_ATOMIC_H */ diff --git a/builtins/flzma2/compiler.h b/builtins/flzma2/compiler.h new file mode 100644 index 0000000000000..b33d18b721d36 --- /dev/null +++ b/builtins/flzma2/compiler.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * Modified for FL2 by Conor McCarthy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef FL2_COMPILER_H +#define FL2_COMPILER_H + +/*-******************************************************* +* Compiler specifics +*********************************************************/ +/* force inlining */ + +#if !defined(FL2_NO_INLINE) +#if defined (__GNUC__) || defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# define INLINE_KEYWORD inline +#else +# define INLINE_KEYWORD +#endif + +#if defined(__GNUC__) +# define FORCE_INLINE_ATTR __attribute__((always_inline)) +#elif defined(_MSC_VER) +# define FORCE_INLINE_ATTR __forceinline +#else +# define FORCE_INLINE_ATTR +#endif + +#else + +#define INLINE_KEYWORD +#define FORCE_INLINE_ATTR + +#endif + +/** + * FORCE_INLINE_TEMPLATE is used to define C "templates", which take constant + * parameters. They must be inlined for the compiler to eliminate the constant + * branches. + */ +#define FORCE_INLINE_TEMPLATE static INLINE_KEYWORD FORCE_INLINE_ATTR +/** + * HINT_INLINE is used to help the compiler generate better code. It is *not* + * used for "templates", so it can be tweaked based on the compilers + * performance. + * + * gcc-4.8 and gcc-4.9 have been shown to benefit from leaving off the + * always_inline attribute. + * + * clang up to 5.0.0 (trunk) benefit tremendously from the always_inline + * attribute. + */ +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 && __GNUC__ < 5 +# define HINT_INLINE static INLINE_KEYWORD +#else +# define HINT_INLINE static INLINE_KEYWORD FORCE_INLINE_ATTR +#endif + +/* force no inlining */ +#ifdef _MSC_VER +# define FORCE_NOINLINE __declspec(noinline) +#else +# ifdef __GNUC__ +# define FORCE_NOINLINE __attribute__((__noinline__)) +# else +# define FORCE_NOINLINE +# endif +#endif + +/* target attribute */ +#ifndef __has_attribute + #define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif +#if defined(__GNUC__) +# define TARGET_ATTRIBUTE(target) __attribute__((__target__(target))) +#else +# define TARGET_ATTRIBUTE(target) +#endif + +/* Enable runtime BMI2 dispatch based on the CPU. + * Enabled for clang & gcc >=4.8 on x86 when BMI2 isn't enabled by default. + */ +#ifndef DYNAMIC_BMI2 + #if ((defined(__clang__) && __has_attribute(__target__)) \ + || (defined(__GNUC__) \ + && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)))) \ + && (defined(__x86_64__) || defined(_M_X86)) \ + && !defined(__BMI2__) + # define DYNAMIC_BMI2 1 + #else + # define DYNAMIC_BMI2 0 + #endif +#endif + +/* prefetch + * can be disabled, by declaring NO_PREFETCH build macro */ +#if defined(NO_PREFETCH) +# define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ +# define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ +#else +# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */ +# include /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ +# define PREFETCH_L1(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) +# define PREFETCH_L2(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T1) +# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +# define PREFETCH_L1(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */) +# define PREFETCH_L2(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 2 /* locality */) +# else +# define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ +# define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ +# endif +#endif /* NO_PREFETCH */ + +#define CACHELINE_SIZE 64 + +#define PREFETCH_AREA(p, s) { \ + const char* const _ptr = (const char*)(p); \ + size_t const _size = (size_t)(s); \ + size_t _pos; \ + for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \ + PREFETCH_L2(_ptr + _pos); \ + } \ +} + +/* disable warnings */ +#ifdef _MSC_VER /* Visual Studio */ +# include /* For Visual 2005 */ +# pragma warning(disable : 4100) /* disable: C4100: unreferenced formal parameter */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4204) /* disable: C4204: non-constant aggregate initializer */ +# pragma warning(disable : 4214) /* disable: C4214: non-int bitfields */ +# pragma warning(disable : 4324) /* disable: C4324: padded structure */ +#endif + +#endif /* FL2_COMPILER_H */ diff --git a/builtins/flzma2/count.h b/builtins/flzma2/count.h new file mode 100644 index 0000000000000..11bf1ef3434c0 --- /dev/null +++ b/builtins/flzma2/count.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef ZSTD_COUNT_H_ +#define ZSTD_COUNT_H_ + +#include "mem.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/*-************************************* +* Match length counter +***************************************/ +static unsigned ZSTD_NbCommonBytes(register size_t val) +{ + if (MEM_isLittleEndian()) { + if (MEM_64bits()) { +# if defined(_MSC_VER) && defined(_WIN64) + unsigned long r = 0; + _BitScanForward64(&r, (U64)val); + return (unsigned)(r >> 3); +# elif defined(__GNUC__) && (__GNUC__ >= 4) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, + 0, 3, 1, 3, 1, 4, 2, 7, + 0, 2, 3, 6, 1, 5, 3, 5, + 1, 3, 4, 4, 2, 5, 6, 7, + 7, 0, 1, 2, 3, 3, 4, 6, + 2, 6, 5, 5, 3, 4, 5, 6, + 7, 1, 2, 4, 6, 4, 4, 5, + 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } + else { /* 32 bits */ +# if defined(_MSC_VER) + unsigned long r = 0; + _BitScanForward(&r, (U32)val); + return (unsigned)(r >> 3); +# elif defined(__GNUC__) && (__GNUC__ >= 3) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, + 3, 2, 2, 1, 3, 2, 0, 1, + 3, 3, 1, 2, 2, 2, 2, 0, + 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } + else { /* Big Endian CPU */ + if (MEM_64bits()) { +# if defined(_MSC_VER) && defined(_WIN64) + unsigned long r = 0; + _BitScanReverse64(&r, val); + return (unsigned)(r >> 3); +# elif defined(__GNUC__) && (__GNUC__ >= 4) + return (__builtin_clzll(val) >> 3); +# else + unsigned r; + const unsigned n32 = sizeof(size_t) * 4; /* calculate this way due to compiler complaining in 32-bits mode */ + if (!(val >> n32)) { r = 4; } + else { r = 0; val >>= n32; } + if (!(val >> 16)) { r += 2; val >>= 8; } + else { val >>= 24; } + r += (!val); + return r; +# endif + } + else { /* 32 bits */ +# if defined(_MSC_VER) + unsigned long r = 0; + _BitScanReverse(&r, (unsigned long)val); + return (unsigned)(r >> 3); +# elif defined(__GNUC__) && (__GNUC__ >= 3) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val >> 16)) { r = 2; val >>= 8; } + else { r = 0; val >>= 24; } + r += (!val); + return r; +# endif + } + } +} + + +static size_t ZSTD_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* const pInLimit) +{ + const BYTE* const pStart = pIn; + const BYTE* const pInLoopLimit = pInLimit - (sizeof(size_t) - 1); + + if (pIn < pInLoopLimit) { + { size_t const diff = MEM_readST(pMatch) ^ MEM_readST(pIn); + if (diff) return ZSTD_NbCommonBytes(diff); } + pIn += sizeof(size_t); pMatch += sizeof(size_t); + while (pIn < pInLoopLimit) { + size_t const diff = MEM_readST(pMatch) ^ MEM_readST(pIn); + if (!diff) { pIn += sizeof(size_t); pMatch += sizeof(size_t); continue; } + pIn += ZSTD_NbCommonBytes(diff); + return (size_t)(pIn - pStart); + } + } + if (MEM_64bits() && (pIn<(pInLimit - 3)) && (MEM_read32(pMatch) == MEM_read32(pIn))) { pIn += 4; pMatch += 4; } + if ((pIn<(pInLimit - 1)) && (MEM_read16(pMatch) == MEM_read16(pIn))) { pIn += 2; pMatch += 2; } + if ((pIn +#include "dict_buffer.h" +#include "fl2_internal.h" + +#define ALIGNMENT_SIZE 16U +#define ALIGNMENT_MASK (~(size_t)(ALIGNMENT_SIZE-1)) + +/* DICT_buffer functions */ + +int DICT_construct(DICT_buffer * const buf, int const async) +{ + buf->data[0] = NULL; + buf->data[1] = NULL; + buf->size = 0; + + buf->async = (async != 0); + +#ifndef NO_XXHASH + buf->xxh = NULL; +#endif + + return 0; +} + +int DICT_init(DICT_buffer * const buf, size_t const dict_size, size_t const overlap, unsigned const reset_multiplier, int const do_hash) +{ + /* Allocate if not yet allocated or existing dict too small */ + if (buf->data[0] == NULL || dict_size > buf->size) { + /* Free any existing buffers */ + DICT_destruct(buf); + + buf->data[0] = malloc(dict_size); + + buf->data[1] = NULL; + if (buf->async) + buf->data[1] = malloc(dict_size); + + if (buf->data[0] == NULL || (buf->async && buf->data[1] == NULL)) { + DICT_destruct(buf); + return 1; + } + } + buf->index = 0; + buf->overlap = overlap; + buf->start = 0; + buf->end = 0; + buf->size = dict_size; + buf->total = 0; + buf->reset_interval = (reset_multiplier != 0) ? dict_size * reset_multiplier : ((size_t)1 << 31); + +#ifndef NO_XXHASH + if (do_hash) { + if (buf->xxh == NULL) { + buf->xxh = XXH32_createState(); + if (buf->xxh == NULL) { + DICT_destruct(buf); + return 1; + } + } + XXH32_reset(buf->xxh, 0); + } + else { + XXH32_freeState(buf->xxh); + buf->xxh = NULL; + } +#else + (void)do_hash; +#endif + + return 0; +} + +void DICT_destruct(DICT_buffer * const buf) +{ + free(buf->data[0]); + free(buf->data[1]); + buf->data[0] = NULL; + buf->data[1] = NULL; + buf->size = 0; +#ifndef NO_XXHASH + XXH32_freeState(buf->xxh); + buf->xxh = NULL; +#endif +} + +size_t DICT_size(const DICT_buffer * const buf) +{ + return buf->size; +} + +/* Get the dictionary buffer for adding input */ +size_t DICT_get(DICT_buffer * const buf, void **const dict) +{ + DICT_shift(buf); + + DEBUGLOG(5, "Getting dict buffer %u, pos %u, avail %u", (unsigned)buf->index, (unsigned)buf->end, (unsigned)(buf->size - buf->end)); + *dict = buf->data[buf->index] + buf->end; + return buf->size - buf->end; +} + +/* Update with the amount added */ +int DICT_update(DICT_buffer * const buf, size_t const added_size) +{ + DEBUGLOG(5, "Added %u bytes to dict buffer %u", (unsigned)added_size, (unsigned)buf->index); + buf->end += added_size; + assert(buf->end <= buf->size); + return !DICT_availSpace(buf); +} + +/* Read from input and write to the dict */ +void DICT_put(DICT_buffer * const buf, FL2_inBuffer * const input) +{ + size_t const to_read = MIN(buf->size - buf->end, input->size - input->pos); + + DEBUGLOG(5, "CStream : reading %u bytes", (U32)to_read); + + memcpy(buf->data[buf->index] + buf->end, (BYTE*)input->src + input->pos, to_read); + + input->pos += to_read; + buf->end += to_read; +} + +size_t DICT_availSpace(const DICT_buffer * const buf) +{ + return buf->size - buf->end; +} + +/* Get the size of uncompressed data. start is set to end after compression */ +int DICT_hasUnprocessed(const DICT_buffer * const buf) +{ + return buf->start < buf->end; +} + +/* Get the buffer, overlap and end for compression */ +void DICT_getBlock(DICT_buffer * const buf, FL2_dataBlock * const block) +{ + block->data = buf->data[buf->index]; + block->start = buf->start; + block->end = buf->end; + +#ifndef NO_XXHASH + if (buf->xxh != NULL) + XXH32_update(buf->xxh, buf->data[buf->index] + buf->start, buf->end - buf->start); +#endif + + buf->total += buf->end - buf->start; + buf->start = buf->end; +} + +/* Shift occurs when all is processed and end is beyond the overlap size */ +int DICT_needShift(DICT_buffer * const buf) +{ + if (buf->start < buf->end) + return 0; + /* Reset the dict if the next compression cycle would exceed the reset interval */ + size_t overlap = (buf->total + buf->size - buf->overlap > buf->reset_interval) ? 0 : buf->overlap; + return buf->start == buf->end && (overlap == 0 || buf->end >= overlap + ALIGNMENT_SIZE); +} + +int DICT_async(const DICT_buffer * const buf) +{ + return (int)buf->async; +} + +/* Shift the overlap amount to the start of either the only dict buffer or the alternate one + * if it exists */ +void DICT_shift(DICT_buffer * const buf) +{ + if (buf->start < buf->end) + return; + + size_t overlap = buf->overlap; + /* Reset the dict if the next compression cycle would exceed the reset interval */ + if (buf->total + buf->size - buf->overlap > buf->reset_interval) { + DEBUGLOG(4, "Resetting dictionary after %u bytes", (unsigned)buf->total); + overlap = 0; + } + + if (overlap == 0) { + /* No overlap means a simple buffer switch */ + buf->start = 0; + buf->end = 0; + buf->index ^= buf->async; + buf->total = 0; + } + else if (buf->end >= overlap + ALIGNMENT_SIZE) { + size_t const from = (buf->end - overlap) & ALIGNMENT_MASK; + const BYTE *const src = buf->data[buf->index]; + /* Copy to the alternate if one exists */ + BYTE *const dst = buf->data[buf->index ^ buf->async]; + + overlap = buf->end - from; + + if (overlap <= from || dst != src) { + DEBUGLOG(5, "Copy overlap data : %u bytes from %u", (unsigned)overlap, (unsigned)from); + memcpy(dst, src + from, overlap); + } + else if (from != 0) { + DEBUGLOG(5, "Move overlap data : %u bytes from %u", (unsigned)overlap, (unsigned)from); + memmove(dst, src + from, overlap); + } + /* New data will be written after the overlap */ + buf->start = overlap; + buf->end = overlap; + /* Switch buffers */ + buf->index ^= buf->async; + } +} + +#ifndef NO_XXHASH +XXH32_hash_t DICT_getDigest(const DICT_buffer * const buf) +{ + return XXH32_digest(buf->xxh); +} +#endif + +size_t DICT_memUsage(const DICT_buffer * const buf) +{ + return (1 + buf->async) * buf->size; +} diff --git a/builtins/flzma2/dict_buffer.h b/builtins/flzma2/dict_buffer.h new file mode 100644 index 0000000000000..436472fb85d4f --- /dev/null +++ b/builtins/flzma2/dict_buffer.h @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include "fast-lzma2.h" +#include "mem.h" +#include "data_block.h" +#ifndef NO_XXHASH +# include "xxhash.h" +#endif + +#ifndef FL2_DICT_BUFFER_H_ +#define FL2_DICT_BUFFER_H_ + +#if defined (__cplusplus) +extern "C" { +#endif + +/* DICT_buffer structure. + * Maintains one or two dictionary buffers. In a dual dict configuration (asyc==1), when the + * current buffer is full, the overlap region will be copied to the other buffer and it + * becomes the destination for input while the first is compressed. This is useful when I/O + * is much slower than compression. */ +typedef struct { + BYTE* data[2]; + size_t index; + size_t async; + size_t overlap; + size_t start; /* start = 0 (first block) or overlap */ + size_t end; /* never < overlap */ + size_t size; /* allocation size */ + size_t total; /* total size compressed after last dict reset */ + size_t reset_interval; +#ifndef NO_XXHASH + XXH32_state_t *xxh; +#endif +} DICT_buffer; + +int DICT_construct(DICT_buffer *const buf, int const async); + +int DICT_init(DICT_buffer *const buf, size_t const dict_size, size_t const overlap, unsigned const reset_multiplier, int const do_hash); + +void DICT_destruct(DICT_buffer *const buf); + +size_t DICT_size(const DICT_buffer *const buf); + +size_t DICT_get(DICT_buffer *const buf, void **const dict); + +int DICT_update(DICT_buffer *const buf, size_t const added_size); + +void DICT_put(DICT_buffer *const buf, FL2_inBuffer* const input); + +size_t DICT_availSpace(const DICT_buffer *const buf); + +int DICT_hasUnprocessed(const DICT_buffer *const buf); + +void DICT_getBlock(DICT_buffer *const buf, FL2_dataBlock *const block); + +int DICT_needShift(DICT_buffer *const buf); + +int DICT_async(const DICT_buffer *const buf); + +void DICT_shift(DICT_buffer *const buf); + +#ifndef NO_XXHASH +XXH32_hash_t DICT_getDigest(const DICT_buffer *const buf); +#endif + +size_t DICT_memUsage(const DICT_buffer *const buf); + +#if defined (__cplusplus) +} +#endif + +#endif /* FL2_DICT_BUFFER_H_ */ \ No newline at end of file diff --git a/builtins/flzma2/fast-lzma2.h b/builtins/flzma2/fast-lzma2.h new file mode 100644 index 0000000000000..1ad31fe279a65 --- /dev/null +++ b/builtins/flzma2/fast-lzma2.h @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2017-present, Conor McCarthy + * All rights reserved. + * Based on zstd.h copyright Yann Collet + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef FAST_LZMA2_H +#define FAST_LZMA2_H + +/* ====== Dependency ======*/ +#include /* size_t */ + + +/* ===== FL2LIB_API : control library symbols visibility ===== */ +#ifndef FL2LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define FL2LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define FL2LIB_VISIBILITY +# endif +#endif +#if defined(FL2_DLL_EXPORT) && (FL2_DLL_EXPORT==1) +# define FL2LIB_API __declspec(dllexport) FL2LIB_VISIBILITY +#elif defined(FL2_DLL_IMPORT) && (FL2_DLL_IMPORT==1) +# define FL2LIB_API __declspec(dllimport) FL2LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define FL2LIB_API FL2LIB_VISIBILITY +#endif + +/* ====== Calling convention ======*/ + +#if !defined _WIN32 || defined __x86_64__s || defined _M_X64 || (defined __SIZEOF_POINTER__ && __SIZEOF_POINTER__ == 8) +# define FL2LIB_CALL +#elif defined(__GNUC__) +# define FL2LIB_CALL __attribute__((cdecl)) +#elif defined(_MSC_VER) +# define FL2LIB_CALL __cdecl +#else +# define FL2LIB_CALL +#endif + +/******************************************************************************************************* +Introduction + +*********************************************************************************************************/ + +/*------ Version ------*/ +#define FL2_VERSION_MAJOR 1 +#define FL2_VERSION_MINOR 0 +#define FL2_VERSION_RELEASE 1 + +#define FL2_VERSION_NUMBER (FL2_VERSION_MAJOR *100*100 + FL2_VERSION_MINOR *100 + FL2_VERSION_RELEASE) +FL2LIB_API unsigned FL2LIB_CALL FL2_versionNumber(void); /**< useful to check dll version */ + +#define FL2_LIB_VERSION FL2_VERSION_MAJOR.FL2_VERSION_MINOR.FL2_VERSION_RELEASE +#define FL2_QUOTE(str) #str +#define FL2_EXPAND_AND_QUOTE(str) FL2_QUOTE(str) +#define FL2_VERSION_STRING FL2_EXPAND_AND_QUOTE(FL2_LIB_VERSION) +FL2LIB_API const char* FL2LIB_CALL FL2_versionString(void); + + +#define FL2_MAXTHREADS 200 + + +/*************************************** +* Simple API +***************************************/ + +/*! FL2_compress() : + * Compresses `src` content as a single LZMA2 compressed stream into already allocated `dst`. + * Call FL2_compressMt() to use > 1 thread. Specify nbThreads = 0 to use all cores. + * @return : compressed size written into `dst` (<= `dstCapacity), + * or an error code if it fails (which can be tested using FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_compress(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel); + +FL2LIB_API size_t FL2LIB_CALL FL2_compressMt(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel, + unsigned nbThreads); + +/*! FL2_decompress() : + * Decompresses a single LZMA2 compressed stream from `src` into already allocated `dst`. + * `compressedSize` : must be at least the size of the LZMA2 stream. + * `dstCapacity` is the original, uncompressed size to regenerate, returned by calling + * FL2_findDecompressedSize(). + * Call FL2_decompressMt() to use > 1 thread. Specify nbThreads = 0 to use all cores. The stream + * must contain dictionary resets to use multiple threads. These are inserted during compression by + * default. The frequency can be changed/disabled with the FL2_p_resetInterval parameter setting. + * @return : the number of bytes decompressed into `dst` (<= `dstCapacity`), + * or an errorCode if it fails (which can be tested using FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_decompress(void* dst, size_t dstCapacity, + const void* src, size_t compressedSize); + +FL2LIB_API size_t FL2LIB_CALL FL2_decompressMt(void* dst, size_t dstCapacity, + const void* src, size_t compressedSize, + unsigned nbThreads); + +/*! FL2_findDecompressedSize() + * `src` should point to the start of a LZMA2 encoded stream. + * `srcSize` must be at least as large as the LZMA2 stream including end marker. + * A property byte is assumed to exist at position 0 in `src`. If the stream was created without one, + * subtract 1 byte from `src` when passing it to the function. + * @return : - decompressed size of the stream in `src`, if known + * - FL2_CONTENTSIZE_ERROR if an error occurred (e.g. corruption, srcSize too small) + * note 1 : a 0 return value means the stream is valid but "empty". + * note 2 : decompressed size can be very large (64-bits value), + * potentially larger than what local system can handle as a single memory segment. + * In which case, it's necessary to use streaming mode to decompress data. + * note 5 : If source is untrusted, decompressed size could be wrong or intentionally modified. + * Always ensure return value fits within application's authorized limits. + * Each application can set its own limits. */ +#define FL2_CONTENTSIZE_ERROR (size_t)-1 +FL2LIB_API unsigned long long FL2LIB_CALL FL2_findDecompressedSize(const void *src, size_t srcSize); + + +/*====== Helper functions ======*/ +FL2LIB_API size_t FL2LIB_CALL FL2_compressBound(size_t srcSize); /*!< maximum compressed size in worst case scenario */ +FL2LIB_API unsigned FL2LIB_CALL FL2_isError(size_t code); /*!< tells if a `size_t` function result is an error code */ +FL2LIB_API unsigned FL2LIB_CALL FL2_isTimedOut(size_t code); /*!< tells if a `size_t` function result is the timeout code */ +FL2LIB_API const char* FL2LIB_CALL FL2_getErrorName(size_t code); /*!< provides readable string from an error code */ +FL2LIB_API int FL2LIB_CALL FL2_maxCLevel(void); /*!< maximum compression level available */ +FL2LIB_API int FL2LIB_CALL FL2_maxHighCLevel(void); /*!< maximum compression level available in high mode */ + + +/*************************************** +* Explicit memory management +***************************************/ + +/*= Compression context + * When compressing many times, it is recommended to allocate a context just once, + * and re-use it for each successive compression operation. This will make workload + * friendlier for system's memory. The context may not use the number of threads requested + * if the library is compiled for single-threaded compression or nbThreads > FL2_MAXTHREADS. + * Call FL2_getCCtxThreadCount to obtain the actual number allocated. */ +typedef struct FL2_CCtx_s FL2_CCtx; +FL2LIB_API FL2_CCtx* FL2LIB_CALL FL2_createCCtx(void); +FL2LIB_API FL2_CCtx* FL2LIB_CALL FL2_createCCtxMt(unsigned nbThreads); +FL2LIB_API void FL2LIB_CALL FL2_freeCCtx(FL2_CCtx* cctx); + +FL2LIB_API unsigned FL2LIB_CALL FL2_getCCtxThreadCount(const FL2_CCtx* cctx); + +/*! FL2_compressCCtx() : + * Same as FL2_compress(), but requires an allocated FL2_CCtx (see FL2_createCCtx()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_compressCCtx(FL2_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel); + +/*! FL2_getCCtxDictProp() : + * Get the dictionary size property. + * Intended for use with the FL2_p_omitProperties parameter for creating a + * 7-zip or XZ compatible LZMA2 stream. */ +FL2LIB_API unsigned char FL2LIB_CALL FL2_getCCtxDictProp(FL2_CCtx* cctx); + + +/**************************** +* Decompression +****************************/ + +/*= Decompression context + * When decompressing many times, it is recommended to allocate a context only once, + * and re-use it for each successive decompression operation. This will make the workload + * friendlier for the system's memory. + * The context may not allocate the number of threads requested if the library is + * compiled for single-threaded compression or nbThreads > FL2_MAXTHREADS. + * Call FL2_getDCtxThreadCount to obtain the actual number allocated. + * At least nbThreads dictionary resets must exist in the stream to use all of the + * threads. Dictionary resets are inserted into the stream according to the + * FL2_p_resetInterval parameter used in the compression context. */ +typedef struct FL2_DCtx_s FL2_DCtx; +FL2LIB_API FL2_DCtx* FL2LIB_CALL FL2_createDCtx(void); +FL2LIB_API FL2_DCtx* FL2LIB_CALL FL2_createDCtxMt(unsigned nbThreads); +FL2LIB_API size_t FL2LIB_CALL FL2_freeDCtx(FL2_DCtx* dctx); + +FL2LIB_API unsigned FL2LIB_CALL FL2_getDCtxThreadCount(const FL2_DCtx* dctx); + + +/*! FL2_initDCtx() : + * Use only when a property byte is not present at input byte 0. No init is necessary otherwise. + * The caller must store the result from FL2_getCCtxDictProp() and pass it to this function. */ +FL2LIB_API size_t FL2LIB_CALL FL2_initDCtx(FL2_DCtx* dctx, unsigned char prop); + +/*! FL2_decompressDCtx() : + * Same as FL2_decompress(), requires an allocated FL2_DCtx (see FL2_createDCtx()) */ +FL2LIB_API size_t FL2LIB_CALL FL2_decompressDCtx(FL2_DCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize); + +/**************************** +* Streaming +****************************/ + +typedef struct { + const void* src; /**< start of input buffer */ + size_t size; /**< size of input buffer */ + size_t pos; /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ +} FL2_inBuffer; + +typedef struct { + void* dst; /**< start of output buffer */ + size_t size; /**< size of output buffer */ + size_t pos; /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ +} FL2_outBuffer; + +/*** Push/pull structs ***/ + +typedef struct { + void* dst; /**< start of available dict buffer */ + unsigned long size; /**< size of dict remaining */ +} FL2_dictBuffer; + +typedef struct { + const void* src; /**< start of compressed data */ + size_t size; /**< size of compressed data */ +} FL2_cBuffer; + +/*-*********************************************************************** + * Streaming compression + * + * A FL2_CStream object is required to track streaming operation. + * Use FL2_createCStream() and FL2_freeCStream() to create/release resources. + * FL2_CStream objects can be reused multiple times on consecutive compression operations. + * It is recommended to re-use FL2_CStream in situations where many streaming operations will be done + * consecutively, since it will reduce allocation and initialization time. + * + * Call FL2_createCStreamMt() with a nonzero dualBuffer parameter to use two input dictionary buffers. + * The stream will not block on FL2_compressStream() and continues to accept data while compression is + * underway, until both buffers are full. Useful when I/O is slow. + * To compress with a single thread with dual buffering, call FL2_createCStreamMt with nbThreads=1. + * + * Use FL2_initCStream() on the FL2_CStream object to start a new compression operation. + * + * Use FL2_compressStream() repetitively to consume input stream. + * The function will automatically update the `pos` field. + * It will always consume the entire input unless an error occurs or the dictionary buffer is filled, + * unlike the decompression function. + * + * The radix match finder allows compressed data to be stored in its match table during encoding. + * Applications may call streaming compression functions with output == NULL. In this case, + * when the function returns 1, the compressed data must be read from the internal buffers. + * Call FL2_getNextCompressedBuffer() repeatedly until it returns 0. + * Each call returns buffer information in the FL2_inBuffer parameter. Applications typically will + * passed this to an I/O write function or downstream filter. + * Alternately, applications may pass an FL2_outBuffer object pointer to receive the output. In this + * case the return value is 1 if the buffer is full and more compressed data remains. + * + * FL2_endStream() instructs to finish a stream. It will perform a flush and write the LZMA2 + * termination byte (required). Call FL2_endStream() repeatedly until it returns 0. + * + * Most functions may return a size_t error code, which can be tested using FL2_isError(). + * + * *******************************************************************/ + +typedef struct FL2_CCtx_s FL2_CStream; + +/*===== FL2_CStream management functions =====*/ +FL2LIB_API FL2_CStream* FL2LIB_CALL FL2_createCStream(void); +FL2LIB_API FL2_CStream* FL2LIB_CALL FL2_createCStreamMt(unsigned nbThreads, int dualBuffer); +FL2LIB_API void FL2LIB_CALL FL2_freeCStream(FL2_CStream * fcs); + +/*===== Streaming compression functions =====*/ + +/*! FL2_initCStream() : + * Call this function before beginning a new compressed data stream. To keep the stream object's + * current parameters, specify zero for the compression level. The object is set to the default + * level upon creation. */ +FL2LIB_API size_t FL2LIB_CALL FL2_initCStream(FL2_CStream* fcs, int compressionLevel); + +/*! FL2_setCStreamTimeout() : + * Sets a timeout in milliseconds. Zero disables the timeout (default). If a nonzero timout is set, functions + * FL2_compressStream(), FL2_getDictionaryBuffer(), FL2_updateDictionary(), FL2_getNextCompressedBuffer(), + * FL2_flushStream(), and FL2_endStream() may return a timeout code before compression of the current + * dictionary of data completes. FL2_isError() returns true for the timeout code, so check the code with + * FL2_isTimedOut() before testing for errors. With the exception of FL2_updateDictionary(), the above + * functions may be called again to wait for completion. A typical application for timeouts is to update the + * user on compression progress. */ +FL2LIB_API size_t FL2LIB_CALL FL2_setCStreamTimeout(FL2_CStream * fcs, unsigned timeout); + +/*! FL2_compressStream() : + * Reads data from input into the dictionary buffer. Compression will begin if the buffer fills up. + * A dual buffering stream will fill the second buffer while compression proceeds on the first. + * A call to FL2_compressStream() will wait for ongoing compression to complete if all dictionary space + * is filled. FL2_compressStream() must not be called with output == NULL unless the caller has read all + * compressed data from the CStream object. + * Returns 1 to indicate compressed data must be read (or output is full), or 0 otherwise. */ +FL2LIB_API size_t FL2LIB_CALL FL2_compressStream(FL2_CStream* fcs, FL2_outBuffer *output, FL2_inBuffer* input); + +/*! FL2_copyCStreamOutput() : + * Copies compressed data to the output buffer until the buffer is full or all available data is copied. + * If asynchronous compression is in progress, the function returns 0 without waiting. + * Returns 1 to indicate some compressed data remains, or 0 otherwise. */ +FL2LIB_API size_t FL2LIB_CALL FL2_copyCStreamOutput(FL2_CStream* fcs, FL2_outBuffer *output); + +/*** Push/pull functions ***/ + +/*! FL2_getDictionaryBuffer() : + * Returns a buffer in the FL2_outBuffer object, which the caller can directly read data into. + * Applications will normally pass this buffer to an I/O read function or upstream filter. + * Returns 0, or an error or timeout code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_getDictionaryBuffer(FL2_CStream* fcs, FL2_dictBuffer* dict); + +/*! FL2_updateDictionary() : + * Informs the CStream how much data was added to the buffer. Compression begins if the dictionary + * was filled. Returns 1 to indicate compressed data must be read, 0 if not, or an error code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_updateDictionary(FL2_CStream* fcs, size_t addedSize); + +/*! FL2_getNextCompressedBuffer() : + * Returns a buffer containing a slice of the compressed data. Call this function and process the data + * until the function returns zero. In most cases it will return a buffer for each compression thread + * used. It is sometimes less but never more than nbThreads. If asynchronous compression is in progress, + * this function will wait for completion before returning, or it will return the timeout code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_getNextCompressedBuffer(FL2_CStream* fcs, FL2_cBuffer* cbuf); + +/******/ + +/*! FL2_getCStreamProgress() : + * Returns the number of bytes processed since the stream was initialized. This is a synthetic + * estimate because the match finder does not proceed sequentially through the data. If + * outputSize is not NULL, returns the number of bytes of compressed data generated. */ +FL2LIB_API unsigned long long FL2LIB_CALL FL2_getCStreamProgress(const FL2_CStream * fcs, unsigned long long *outputSize); + +/*! FL2_waitCStream() : + * Waits for compression to end. This function returns after the timeout set using + * FL2_setCStreamTimeout has elapsed. Unnecessary when no timeout is set. + * Returns 1 if compressed output is available, 0 if not, or the timeout code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_waitCStream(FL2_CStream * fcs); + +/*! FL2_cancelCStream() : + * Cancels any compression operation underway. Useful only when dual buffering and/or timeouts + * are enabled. The stream will be returned to an uninitialized state. */ +FL2LIB_API void FL2LIB_CALL FL2_cancelCStream(FL2_CStream *fcs); + +/*! FL2_remainingOutputSize() : + * The amount of compressed data remaining to be read from the CStream object. */ +FL2LIB_API size_t FL2LIB_CALL FL2_remainingOutputSize(const FL2_CStream* fcs); + +/*! FL2_flushStream() : + * Compress all data remaining in the dictionary buffer(s). It may be necessary to call + * FL2_flushStream() more than once. If output == NULL the compressed data must be read from the + * CStream object after each call. + * Flushing is not normally useful and produces larger output. + * Returns 1 if input or output still exists in the CStream object, 0 if complete, or an error code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_flushStream(FL2_CStream* fcs, FL2_outBuffer *output); + +/*! FL2_endStream() : + * Compress all data remaining in the dictionary buffer(s) and write the stream end marker. It may + * be necessary to call FL2_endStream() more than once. If output == NULL the compressed data must + * be read from the CStream object after each call. + * Returns 0 when compression is complete and all output has been flushed, 1 if not complete, or + * an error code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_endStream(FL2_CStream* fcs, FL2_outBuffer *output); + +/*-*************************************************************************** + * Streaming decompression + * + * A FL2_DStream object is required to track streaming operations. + * Use FL2_createDStream() and FL2_freeDStream() to create/release resources. + * FL2_DStream objects can be re-used multiple times. + * + * Use FL2_initDStream() to start a new decompression operation. + * @return : zero or an error code + * + * Use FL2_decompressStream() repetitively to consume your input. + * The function will update both `pos` fields. + * If `input.pos < input.size`, some input has not been consumed. + * It's up to the caller to present again the remaining data. + * If `output.pos < output.size`, decoder has flushed everything it could. + * @return : 0 when a stream is completely decoded and fully flushed, + * 1, which means there is still some decoding to do to complete the stream, + * or an error code, which can be tested using FL2_isError(). + * *******************************************************************************/ + +typedef struct FL2_DStream_s FL2_DStream; + +/*===== FL2_DStream management functions =====*/ +FL2LIB_API FL2_DStream* FL2LIB_CALL FL2_createDStream(void); +FL2LIB_API FL2_DStream* FL2LIB_CALL FL2_createDStreamMt(unsigned nbThreads); +FL2LIB_API size_t FL2LIB_CALL FL2_freeDStream(FL2_DStream* fds); + +/*! FL2_setDStreamMemoryLimitMt() : + * Set a total size limit for multithreaded decoder input and output buffers. MT decoder memory + * usage is unknown until the input is parsed. If the limit is exceeded, the decoder switches to + * using a single thread. + * MT decoding memory usage is typically dictionary_size * 4 * nbThreads for the output + * buffers plus the size of the compressed input for that amount of output. */ +FL2LIB_API void FL2LIB_CALL FL2_setDStreamMemoryLimitMt(FL2_DStream* fds, size_t limit); + +/*! FL2_setDStreamTimeout() : + * Sets a timeout in milliseconds. Zero disables the timeout. If a nonzero timout is set, + * FL2_decompressStream() may return a timeout code before decompression of the available data + * completes. FL2_isError() returns true for the timeout code, so check the code with FL2_isTimedOut() + * before testing for errors. After a timeout occurs, do not call FL2_decompressStream() again unless + * a call to FL2_waitDStream() returns 1. A typical application for timeouts is to update the user on + * decompression progress. */ +FL2LIB_API size_t FL2LIB_CALL FL2_setDStreamTimeout(FL2_DStream * fds, unsigned timeout); + +/*! FL2_waitDStream() : + * Waits for decompression to end after a timeout has occurred. This function returns after the + * timeout set using FL2_setDStreamTimeout() has elapsed, or when decompression of available input is + * complete. Unnecessary when no timeout is set. + * Returns 0 if the stream is complete, 1 if not complete, or an error code. */ +FL2LIB_API size_t FL2LIB_CALL FL2_waitDStream(FL2_DStream * fds); + +/*! FL2_cancelDStream() : + * Frees memory allocated for MT decoding. If a timeout is set and the caller is waiting + * for completion of MT decoding, decompression in progress will be canceled. */ +FL2LIB_API void FL2LIB_CALL FL2_cancelDStream(FL2_DStream *fds); + +/*! FL2_getDStreamProgress() : + * Returns the number of bytes decoded since the stream was initialized. */ +FL2LIB_API unsigned long long FL2LIB_CALL FL2_getDStreamProgress(const FL2_DStream * fds); + +/*===== Streaming decompression functions =====*/ + +/*! FL2_initDStream() : + * Call this function before decompressing a stream. FL2_initDStream_withProp() + * must be used for streams which do not include a property byte at position zero. + * The caller is responsible for storing and passing the property byte. + * Returns 0 if okay, or an error if the stream object is still in use from a + * previous call to FL2_decompressStream() (see timeout info above). */ +FL2LIB_API size_t FL2LIB_CALL FL2_initDStream(FL2_DStream* fds); +FL2LIB_API size_t FL2LIB_CALL FL2_initDStream_withProp(FL2_DStream* fds, unsigned char prop); + +/*! FL2_decompressStream() : + * Reads data from input and decompresses to output. + * Returns 1 if the stream is unfinished, 0 if the terminator was encountered (he'll be back) + * and all data was written to output, or an error code. Call this function repeatedly if + * necessary, removing data from output and/or loading data into input before each call. */ +FL2LIB_API size_t FL2LIB_CALL FL2_decompressStream(FL2_DStream* fds, FL2_outBuffer* output, FL2_inBuffer* input); + +/*-*************************************************************************** + * Compression parameters + * + * Any function that takes a 'compressionLevel' parameter will replace any + * parameters affected by compression level that are already set. + * To use a preset level and modify it, call FL2_CCtx_setParameter with + * FL2_p_compressionLevel to set the level, then call FL2_CCtx_setParameter again + * with any other settings to change. + * Specify a compressionLevel of 0 when calling a compression function to keep + * the current parameters. + * *******************************************************************************/ + +#define FL2_DICTLOG_MIN 20 +#define FL2_DICTLOG_MAX_32 27 +#define FL2_DICTLOG_MAX_64 30 +#define FL2_DICTLOG_MAX ((unsigned)(sizeof(size_t) == 4 ? FL2_DICTLOG_MAX_32 : FL2_DICTLOG_MAX_64)) +#define FL2_DICTSIZE_MAX (1U << FL2_DICTLOG_MAX) +#define FL2_DICTSIZE_MIN (1U << FL2_DICTLOG_MIN) +#define FL2_BLOCK_OVERLAP_MIN 0 +#define FL2_BLOCK_OVERLAP_MAX 14 +#define FL2_RESET_INTERVAL_MIN 1 +#define FL2_RESET_INTERVAL_MAX 16 /* small enough to fit FL2_DICTSIZE_MAX * FL2_RESET_INTERVAL_MAX in 32-bit size_t */ +#define FL2_BUFFER_RESIZE_MIN 0 +#define FL2_BUFFER_RESIZE_MAX 4 +#define FL2_BUFFER_RESIZE_DEFAULT 2 +#define FL2_CHAINLOG_MIN 4 +#define FL2_CHAINLOG_MAX 14 +#define FL2_HYBRIDCYCLES_MIN 1 +#define FL2_HYBRIDCYCLES_MAX 64 +#define FL2_SEARCH_DEPTH_MIN 6 +#define FL2_SEARCH_DEPTH_MAX 254 +#define FL2_FASTLENGTH_MIN 6 /* only used by optimizer */ +#define FL2_FASTLENGTH_MAX 273 /* only used by optimizer */ +#define FL2_LC_MIN 0 +#define FL2_LC_MAX 4 +#define FL2_LP_MIN 0 +#define FL2_LP_MAX 4 +#define FL2_PB_MIN 0 +#define FL2_PB_MAX 4 +#define FL2_LCLP_MAX 4 + +typedef enum { + FL2_fast, + FL2_opt, + FL2_ultra +} FL2_strategy; + +typedef struct { + size_t dictionarySize; /* largest match distance : larger == more compression, more memory needed during decompression; > 64Mb == more memory per byte, slower */ + unsigned overlapFraction; /* overlap between consecutive blocks in 1/16 units: larger == more compression, slower */ + unsigned chainLog; /* HC3 sliding window : larger == more compression, slower; hybrid mode only (ultra) */ + unsigned cyclesLog; /* nb of searches : larger == more compression, slower; hybrid mode only (ultra) */ + unsigned searchDepth; /* maximum depth for resolving string matches : larger == more compression, slower */ + unsigned fastLength; /* acceptable match size for parser : larger == more compression, slower; fast bytes parameter from 7-Zip */ + unsigned divideAndConquer; /* split long chains of 2-byte matches into shorter chains with a small overlap : faster, somewhat less compression; enabled by default */ + FL2_strategy strategy; /* encoder strategy : fast, optimized or ultra (hybrid) */ +} FL2_compressionParameters; + +typedef enum { + /* compression parameters */ + FL2_p_compressionLevel, /* Update all compression parameters according to pre-defined cLevel table + * Default level is FL2_CLEVEL_DEFAULT==6. + * Setting FL2_p_highCompression to 1 switches to an alternate cLevel table. */ + FL2_p_highCompression, /* Maximize compression ratio for a given dictionary size. + * Levels 1..10 = dictionaryLog 20..29 (1 Mb..512 Mb). + * Typically provides a poor speed/ratio tradeoff. */ + FL2_p_dictionaryLog, /* Maximum allowed back-reference distance, expressed as power of 2. + * Must be clamped between FL2_DICTLOG_MIN and FL2_DICTLOG_MAX. + * Default = 24 */ + FL2_p_dictionarySize, /* Same as above but expressed as an absolute value. + * Must be clamped between FL2_DICTSIZE_MIN and FL2_DICTSIZE_MAX. + * Default = 16 Mb */ + FL2_p_overlapFraction, /* The radix match finder is block-based, so some overlap is retained from + * each block to improve compression of the next. This value is expressed + * as n / 16 of the block size (dictionary size). Larger values are slower. + * Values above 2 mostly yield only a small improvement in compression. + * A large value for a small dictionary may worsen multithreaded compression. + * Default = 2 */ + FL2_p_resetInterval, /* For multithreaded decompression. A dictionary reset will occur + * after each dictionarySize * resetInterval bytes of input. + * Default = 4 */ + FL2_p_bufferResize, /* Buffering speeds up the matchfinder. Buffer resize determines the percentage of + * the normal buffer size used, which depends on dictionary size. + * 0=50, 1=75, 2=100, 3=150, 4=200. Higher number = slower, better + * compression, higher memory usage. A CPU with a large memory cache + * may make effective use of a larger buffer. + * Default = 2 */ + FL2_p_hybridChainLog, /* Size of the hybrid mode HC3 hash chain, as a power of 2. + * Resulting table size is (1 << (chainLog+2)) bytes. + * Larger tables result in better and slower compression. + * This parameter is only used by the hybrid "ultra" strategy. + * Default = 9 */ + FL2_p_hybridCycles, /* Number of search attempts made by the HC3 match finder. + * Used only by the hybrid "ultra" strategy. + * More attempts result in slightly better and slower compression. + * Default = 1 */ + FL2_p_searchDepth, /* Match finder will resolve string matches up to this length. If a longer + * match exists further back in the input, it will not be found. + * Default = 42 */ + FL2_p_fastLength, /* Only useful for strategies >= opt. + * Length of match considered "good enough" to stop search. + * Larger values make compression stronger and slower. + * Default = 48 */ + FL2_p_divideAndConquer, /* Split long chains of 2-byte matches into shorter chains with a small overlap + * for further processing. Allows buffering of all chains at length 2. + * Faster, less compression. Generally a good tradeoff. + * Default = enabled */ + FL2_p_strategy, /* 1 = fast; 2 = optimized, 3 = ultra (hybrid mode). + * The higher the value of the selected strategy, the more complex it is, + * resulting in stronger and slower compression. + * Default = ultra */ + FL2_p_literalCtxBits, /* lc value for LZMA2 encoder + * Default = 3 */ + FL2_p_literalPosBits, /* lp value for LZMA2 encoder + * Default = 0 */ + FL2_p_posBits, /* pb value for LZMA2 encoder + * Default = 2 */ + FL2_p_omitProperties, /* Omit the property byte at the start of the stream. For use within 7-zip */ + /* or other containers which store the property byte elsewhere. */ + /* A stream compressed under this setting cannot be decoded by this library. */ +#ifndef NO_XXHASH + FL2_p_doXXHash, /* Calculate a 32-bit xxhash value from the input data and store it + * after the stream terminator. The value will be checked on decompression. + * 0 = do not calculate; 1 = calculate (default) */ +#endif +#ifdef RMF_REFERENCE + FL2_p_useReferenceMF /* Use the reference matchfinder for development purposes. SLOW. */ +#endif +} FL2_cParameter; + + +/*! FL2_CCtx_setParameter() : + * Set one compression parameter, selected by enum FL2_cParameter. + * @result : informational value (typically, the one being set, possibly corrected), + * or an error code (which can be tested with FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_CCtx_setParameter(FL2_CCtx* cctx, FL2_cParameter param, size_t value); + +/*! FL2_CCtx_getParameter() : + * Get one compression parameter, selected by enum FL2_cParameter. + * @result : the parameter value, or the parameter_unsupported error code + * (which can be tested with FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_CCtx_getParameter(FL2_CCtx* cctx, FL2_cParameter param); + +/*! FL2_CStream_setParameter() : + * Set one compression parameter, selected by enum FL2_cParameter. + * @result : informational value (typically, the one being set, possibly corrected), + * or an error code (which can be tested with FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_CStream_setParameter(FL2_CStream* fcs, FL2_cParameter param, size_t value); + +/*! FL2_CStream_getParameter() : + * Get one compression parameter, selected by enum FL2_cParameter. + * @result : the parameter value, or the parameter_unsupported error code + * (which can be tested with FL2_isError()). */ +FL2LIB_API size_t FL2LIB_CALL FL2_CStream_getParameter(FL2_CStream* fcs, FL2_cParameter param); + +/*! FL2_getLevelParameters() : + * Get all compression parameter values defined by the preset compressionLevel. + * @result : the values in a FL2_compressionParameters struct, or the parameter_outOfBound error code + * (which can be tested with FL2_isError()) if compressionLevel is invalid. */ +FL2LIB_API size_t FL2LIB_CALL FL2_getLevelParameters(int compressionLevel, int high, FL2_compressionParameters *params); + + +/*************************************** +* Context memory usage +***************************************/ + +/*! FL2_estimate*() : +* These functions estimate memory usage of a CCtx before its creation or before any operation has begun. +* FL2_estimateCCtxSize() will provide a budget large enough for any compression level up to selected one. +* To use FL2_estimateCCtxSize_usingCCtx, set the compression level and any other settings for the context, +* then call the function. Some allocation occurs when the context is created, but the large memory buffers +* used for string matching are allocated only when compression is initialized. */ + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize(int compressionLevel, unsigned nbThreads); /*!< memory usage determined by level */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize_byParams(const FL2_compressionParameters *params, unsigned nbThreads); /*!< memory usage determined by params */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize_usingCCtx(const FL2_CCtx* cctx); /*!< memory usage determined by settings */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize(int compressionLevel, unsigned nbThreads, int dualBuffer); /*!< memory usage determined by level */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize_byParams(const FL2_compressionParameters *params, unsigned nbThreads, int dualBuffer); /*!< memory usage determined by params */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize_usingCStream(const FL2_CStream* fcs); /*!< memory usage determined by settings */ + +/*! FL2_getDictSizeFromProp() : + * Get the dictionary size from the property byte for a stream. The property byte is the first byte +* in the stream, unless omitProperties was enabled, in which case the caller must store it. */ +FL2LIB_API size_t FL2LIB_CALL FL2_getDictSizeFromProp(unsigned char prop); + +/*! FL2_estimateDCtxSize() : + * The size of a DCtx does not include a dictionary buffer because the caller must supply one. */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateDCtxSize(unsigned nbThreads); + +/*! FL2_estimateDStreamSize() : + * Estimate decompression memory use from the dictionary size and number of threads. + * For nbThreads == 0 the number of available cores will be used. + * Obtain dictSize by passing the property byte to FL2_getDictSizeFromProp. */ +FL2LIB_API size_t FL2LIB_CALL FL2_estimateDStreamSize(size_t dictSize, unsigned nbThreads); /*!< obtain dictSize from FL2_getDictSizeFromProp() */ + +#endif /* FAST_LZMA2_H */ + +#if defined (__cplusplus) +} +#endif diff --git a/builtins/flzma2/fastpos_table.h b/builtins/flzma2/fastpos_table.h new file mode 100644 index 0000000000000..fabe2852dad15 --- /dev/null +++ b/builtins/flzma2/fastpos_table.h @@ -0,0 +1,262 @@ +/* This file has been automatically generated by fastpos_tablegen.c. */ +/* Copied from the XZ project */ + + +static const BYTE distance_table[1 << kFastDistBits] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23 +}; diff --git a/builtins/flzma2/fl2_common.c b/builtins/flzma2/fl2_common.c new file mode 100644 index 0000000000000..1d96a55aae321 --- /dev/null +++ b/builtins/flzma2/fl2_common.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * Modified for FL2 by Conor McCarthy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + + + +/*-************************************* +* Dependencies +***************************************/ +#include "fast-lzma2.h" +#include "fl2_errors.h" +#include "fl2_internal.h" +#include "lzma2_enc.h" + + +/*-**************************************** +* Version +******************************************/ +FL2LIB_API unsigned FL2LIB_CALL FL2_versionNumber(void) { return FL2_VERSION_NUMBER; } + +FL2LIB_API const char* FL2LIB_CALL FL2_versionString(void) { return FL2_VERSION_STRING; } + + +/*-**************************************** +* Compression helpers +******************************************/ +FL2LIB_API size_t FL2LIB_CALL FL2_compressBound(size_t srcSize) +{ + return LZMA2_compressBound(srcSize); +} + +/*-**************************************** +* FL2 Error Management +******************************************/ +HINT_INLINE +unsigned IsError(size_t code) +{ + return (code > FL2_ERROR(maxCode)); +} + +/*! FL2_isError() : + * tells if a return value is an error code */ +FL2LIB_API unsigned FL2LIB_CALL FL2_isError(size_t code) +{ + return IsError(code); +} + +/*! FL2_isTimedOut() : + * tells if a return value is the timeout code */ +FL2LIB_API unsigned FL2LIB_CALL FL2_isTimedOut(size_t code) +{ + return (code == FL2_ERROR(timedOut)); +} + +/*! FL2_getErrorName() : + * provides error code string from function result (useful for debugging) */ +FL2LIB_API const char* FL2LIB_CALL FL2_getErrorName(size_t code) +{ + return FL2_getErrorString(FL2_getErrorCode(code)); +} + +/*! FL2_getError() : + * convert a `size_t` function result into a proper FL2_errorCode enum */ +FL2LIB_API FL2_ErrorCode FL2LIB_CALL FL2_getErrorCode(size_t code) +{ + if (!IsError(code)) + return (FL2_ErrorCode)0; + + return (FL2_ErrorCode)(0 - code); +} + +/*! FL2_getErrorString() : + * provides error code string from enum */ +FL2LIB_API const char* FL2LIB_CALL FL2_getErrorString(FL2_ErrorCode code) +{ + static const char* const notErrorCode = "Unspecified error code"; + switch (code) + { + case PREFIX(no_error): return "No error detected"; + case PREFIX(GENERIC): return "Error (generic)"; + case PREFIX(internal): return "Internal error (bug)"; + case PREFIX(corruption_detected): return "Corrupted block detected"; + case PREFIX(checksum_wrong): return "Restored data doesn't match checksum"; + case PREFIX(parameter_unsupported): return "Unsupported parameter"; + case PREFIX(parameter_outOfBound): return "Parameter is out of bound"; + case PREFIX(lclpMax_exceeded): return "Parameters lc+lp > 4"; + case PREFIX(stage_wrong): return "Not possible at this stage of encoding"; + case PREFIX(init_missing): return "Context should be init first"; + case PREFIX(memory_allocation): return "Allocation error : not enough memory"; + case PREFIX(dstSize_tooSmall): return "Destination buffer is too small"; + case PREFIX(srcSize_wrong): return "Src size is incorrect"; + case PREFIX(canceled): return "Processing was canceled by a call to FL2_cancelCStream() or FL2_cancelDStream()"; + case PREFIX(buffer): return "Streaming progress halted due to buffer(s) full/empty"; + case PREFIX(timedOut): return "Wait timed out. Timeouts should be handled before errors using FL2_isTimedOut()"; + /* following error codes are not stable and may be removed or changed in a future version */ + case PREFIX(maxCode): + default: return notErrorCode; + } +} + +/*! g_debuglog_enable : + * turn on/off debug traces (global switch) */ +#if defined(FL2_DEBUG) && (FL2_DEBUG >= 2) +int g_debuglog_enable = 1; +#endif + diff --git a/builtins/flzma2/fl2_compress.c b/builtins/flzma2/fl2_compress.c new file mode 100644 index 0000000000000..ab15ef9bb6e3a --- /dev/null +++ b/builtins/flzma2/fl2_compress.c @@ -0,0 +1,1310 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* Parts based on zstd_compress.c copyright Yann Collet +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include +#include "fast-lzma2.h" +#include "fl2_errors.h" +#include "fl2_internal.h" +#include "platform.h" +#include "mem.h" +#include "util.h" +#include "fl2_compress_internal.h" +#include "fl2_threading.h" +#include "fl2_pool.h" +#include "radix_mf.h" +#include "lzma2_enc.h" + +#define FL2_MAX_LOOPS 10U + +/*-===== Pre-defined compression levels =====-*/ + +#define MB *(1U<<20) + +#define FL2_MAX_HIGH_CLEVEL 10 + +#ifdef FL2_XZ_BUILD + +#define FL2_CLEVEL_DEFAULT 6 +#define FL2_MAX_CLEVEL 9 + +static const FL2_compressionParameters FL2_defaultCParameters[FL2_MAX_CLEVEL + 1] = { + { 0,0,0,0,0,0,0,0 }, + { 1 MB, 1, 7, 0, 6, 32, 1, FL2_fast }, /* 1 */ + { 2 MB, 2, 7, 0, 14, 32, 1, FL2_fast }, /* 2 */ + { 2 MB, 2, 7, 0, 14, 40, 1, FL2_opt }, /* 3 */ + { 8 MB, 2, 7, 0, 26, 40, 1, FL2_opt }, /* 4 */ + { 16 MB, 2, 8, 0, 42, 48, 1, FL2_opt }, /* 5 */ + { 16 MB, 2, 9, 1, 42, 48, 1, FL2_ultra }, /* 6 */ + { 32 MB, 2, 10, 1, 50, 64, 1, FL2_ultra }, /* 7 */ + { 64 MB, 2, 11, 2, 62, 96, 1, FL2_ultra }, /* 8 */ + { 128 MB, 2, 12, 3, 90, 128, 1, FL2_ultra }, /* 9 */ +}; + +#elif defined(FL2_7ZIP_BUILD) + +#define FL2_CLEVEL_DEFAULT 5 +#define FL2_MAX_CLEVEL 9 + +static const FL2_compressionParameters FL2_defaultCParameters[FL2_MAX_CLEVEL + 1] = { + { 0,0,0,0,0,0,0,0 }, + { 1 MB, 1, 7, 0, 6, 32, 1, FL2_fast }, /* 1 */ + { 2 MB, 2, 7, 0, 10, 32, 1, FL2_fast }, /* 2 */ + { 2 MB, 2, 7, 0, 10, 32, 1, FL2_opt }, /* 3 */ + { 4 MB, 2, 7, 0, 14, 32, 1, FL2_opt }, /* 4 */ + { 16 MB, 2, 9, 0, 42, 48, 1, FL2_ultra }, /* 5 */ + { 32 MB, 2, 10, 0, 50, 64, 1, FL2_ultra }, /* 6 */ + { 64 MB, 2, 11, 1, 62, 96, 1, FL2_ultra }, /* 7 */ + { 64 MB, 4, 12, 2, 90, 273, 1, FL2_ultra }, /* 8 */ + { 128 MB, 2, 14, 3, 254, 273, 0, FL2_ultra } /* 9 */ +}; + +#else + +#define FL2_CLEVEL_DEFAULT 6 +#define FL2_MAX_CLEVEL 10 + +static const FL2_compressionParameters FL2_defaultCParameters[FL2_MAX_CLEVEL + 1] = { + { 0,0,0,0,0,0,0,0 }, + { 1 MB, 1, 7, 0, 6, 32, 1, FL2_fast }, /* 1 */ + { 2 MB, 2, 7, 0, 10, 32, 1, FL2_fast }, /* 2 */ + { 2 MB, 2, 7, 0, 10, 32, 1, FL2_opt }, /* 3 */ + { 4 MB, 2, 7, 0, 26, 40, 1, FL2_opt }, /* 4 */ + { 8 MB, 2, 8, 0, 42, 48, 1, FL2_opt }, /* 5 */ + { 16 MB, 2, 9, 0, 42, 48, 1, FL2_ultra }, /* 6 */ + { 32 MB, 2, 10, 0, 50, 64, 1, FL2_ultra }, /* 7 */ + { 64 MB, 2, 11, 1, 62, 96, 1, FL2_ultra }, /* 8 */ + { 64 MB, 4, 12, 2, 90, 273, 1, FL2_ultra }, /* 9 */ + { 128 MB, 2, 14, 3, 254, 273, 0, FL2_ultra } /* 10 */ +}; + +#endif + +static const FL2_compressionParameters FL2_highCParameters[FL2_MAX_HIGH_CLEVEL + 1] = { + { 0,0,0,0,0,0,0,0 }, + { 1 MB, 4, 9, 2, 254, 273, 0, FL2_ultra }, /* 1 */ + { 2 MB, 4, 10, 2, 254, 273, 0, FL2_ultra }, /* 2 */ + { 4 MB, 4, 11, 2, 254, 273, 0, FL2_ultra }, /* 3 */ + { 8 MB, 4, 12, 2, 254, 273, 0, FL2_ultra }, /* 4 */ + { 16 MB, 4, 13, 3, 254, 273, 0, FL2_ultra }, /* 5 */ + { 32 MB, 4, 14, 3, 254, 273, 0, FL2_ultra }, /* 6 */ + { 64 MB, 4, 14, 4, 254, 273, 0, FL2_ultra }, /* 7 */ + { 128 MB, 4, 14, 4, 254, 273, 0, FL2_ultra }, /* 8 */ + { 256 MB, 4, 14, 5, 254, 273, 0, FL2_ultra }, /* 9 */ + { 512 MB, 4, 14, 5, 254, 273, 0, FL2_ultra } /* 10 */ +}; + +#undef MB + +FL2LIB_API int FL2LIB_CALL FL2_maxCLevel(void) +{ + return FL2_MAX_CLEVEL; +} + +FL2LIB_API int FL2LIB_CALL FL2_maxHighCLevel(void) +{ + return FL2_MAX_HIGH_CLEVEL; +} + +static void FL2_fillParameters(FL2_CCtx* const cctx, const FL2_compressionParameters* const params) +{ + FL2_lzma2Parameters* const cParams = &cctx->params.cParams; + cParams->lc = 3; + cParams->lp = 0; + cParams->pb = 2; + cParams->fast_length = params->fastLength; + cParams->match_cycles = 1U << params->cyclesLog; + cParams->strategy = params->strategy; + cParams->second_dict_bits = params->chainLog; + + RMF_parameters* const rParams = &cctx->params.rParams; + rParams->dictionary_size = MIN(params->dictionarySize, FL2_DICTSIZE_MAX); /* allows for reduced dict in 32-bit version */ + rParams->match_buffer_resize = FL2_BUFFER_RESIZE_DEFAULT; + rParams->overlap_fraction = params->overlapFraction; + rParams->divide_and_conquer = params->divideAndConquer; + rParams->depth = params->searchDepth; +#ifdef RMF_REFERENCE + rParams->use_ref_mf = 1; +#endif +} + +static FL2_CCtx* FL2_createCCtx_internal(unsigned nbThreads, int const dualBuffer) +{ + nbThreads = FL2_checkNbThreads(nbThreads); + + DEBUGLOG(3, "FL2_createCCtxMt : %u threads", nbThreads); + + FL2_CCtx* const cctx = calloc(1, sizeof(FL2_CCtx) + (nbThreads - 1) * sizeof(FL2_job)); + if (cctx == NULL) + return NULL; + + cctx->jobCount = nbThreads; + for (unsigned u = 0; u < nbThreads; ++u) + cctx->jobs[u].enc = NULL; + +#ifndef NO_XXHASH + cctx->params.doXXH = 1; +#endif + + cctx->matchTable = NULL; + +#ifndef FL2_SINGLETHREAD + cctx->compressThread = NULL; + cctx->factory = FL2POOL_create(nbThreads - 1); + if (nbThreads > 1 && cctx->factory == NULL) { + FL2_freeCCtx(cctx); + return NULL; + } + if (dualBuffer) { + cctx->compressThread = FL2POOL_create(1); + if (cctx->compressThread == NULL) + return NULL; + } +#endif + + for (unsigned u = 0; u < nbThreads; ++u) { + cctx->jobs[u].enc = LZMA2_createECtx(); + if (cctx->jobs[u].enc == NULL) { + FL2_freeCCtx(cctx); + return NULL; + } + cctx->jobs[u].cctx = cctx; + } + + DICT_construct(&cctx->buf, dualBuffer); + + FL2_CCtx_setParameter(cctx, FL2_p_compressionLevel, FL2_CLEVEL_DEFAULT); + cctx->params.cParams.reset_interval = 4; + + return cctx; +} + +FL2LIB_API FL2_CCtx* FL2LIB_CALL FL2_createCCtx(void) +{ + return FL2_createCCtx_internal(1, 0); +} + +FL2LIB_API FL2_CCtx* FL2LIB_CALL FL2_createCCtxMt(unsigned nbThreads) +{ + return FL2_createCCtx_internal(nbThreads, 0); +} + +FL2LIB_API void FL2LIB_CALL FL2_freeCCtx(FL2_CCtx* cctx) +{ + if (cctx == NULL) + return; + + DEBUGLOG(3, "FL2_freeCCtx : %u threads", cctx->jobCount); + + DICT_destruct(&cctx->buf); + + for (unsigned u = 0; u < cctx->jobCount; ++u) { + LZMA2_freeECtx(cctx->jobs[u].enc); + } + +#ifndef FL2_SINGLETHREAD + FL2POOL_free(cctx->factory); + FL2POOL_free(cctx->compressThread); +#endif + + RMF_freeMatchTable(cctx->matchTable); + free(cctx); +} + +FL2LIB_API unsigned FL2LIB_CALL FL2_getCCtxThreadCount(const FL2_CCtx* cctx) +{ + return cctx->jobCount; +} + +/* FL2_buildRadixTable() : FL2POOL_function type */ +static void FL2_buildRadixTable(void* const jobDescription, ptrdiff_t const n) +{ + FL2_CCtx* const cctx = (FL2_CCtx*)jobDescription; + + RMF_buildTable(cctx->matchTable, n, 1, cctx->curBlock); +} + +/* FL2_compressRadixChunk() : FL2POOL_function type */ +static void FL2_compressRadixChunk(void* const jobDescription, ptrdiff_t const n) +{ + FL2_CCtx* const cctx = (FL2_CCtx*)jobDescription; + + cctx->jobs[n].cSize = LZMA2_encode(cctx->jobs[n].enc, cctx->matchTable, + cctx->jobs[n].block, + &cctx->params.cParams, + -1, + &cctx->progressIn, &cctx->progressOut, &cctx->canceled); +} + +static int FL2_initEncoders(FL2_CCtx* const cctx) +{ + for(unsigned u = 0; u < cctx->jobCount; ++u) { + if (LZMA2_hashAlloc(cctx->jobs[u].enc, &cctx->params.cParams) != 0) + return 1; + } + return 0; +} + +static void FL2_initProgress(FL2_CCtx* const cctx) +{ + RMF_initProgress(cctx->matchTable); + cctx->progressIn = 0; + cctx->streamCsize += cctx->progressOut; + cctx->progressOut = 0; + cctx->canceled = 0; +} + +/* FL2_compressCurBlock_blocking() : + * Compress cctx->curBlock and wait until complete. + * Write streamProp as the first byte if >= 0 + */ +static size_t FL2_compressCurBlock_blocking(FL2_CCtx* const cctx, int const streamProp) +{ + size_t const encodeSize = (cctx->curBlock.end - cctx->curBlock.start); +#ifndef FL2_SINGLETHREAD + size_t mfThreads = cctx->curBlock.end / RMF_MIN_BYTES_PER_THREAD; + size_t nbThreads = MIN(cctx->jobCount, encodeSize / ENC_MIN_BYTES_PER_THREAD); + nbThreads += !nbThreads; +#else + size_t mfThreads = 1; + size_t nbThreads = 1; +#endif + + DEBUGLOG(5, "FL2_compressCurBlock : %u threads, %u start, %u bytes", (U32)nbThreads, (U32)cctx->curBlock.start, (U32)encodeSize); + + size_t sliceStart = cctx->curBlock.start; + size_t const sliceSize = encodeSize / nbThreads; + cctx->jobs[0].block.data = cctx->curBlock.data; + cctx->jobs[0].block.start = sliceStart; + cctx->jobs[0].block.end = sliceStart + sliceSize; + + for (size_t u = 1; u < nbThreads; ++u) { + sliceStart += sliceSize; + cctx->jobs[u].block.data = cctx->curBlock.data; + cctx->jobs[u].block.start = sliceStart; + cctx->jobs[u].block.end = sliceStart + sliceSize; + } + cctx->jobs[nbThreads - 1].block.end = cctx->curBlock.end; + + /* initialize to length 2 */ + RMF_initTable(cctx->matchTable, cctx->curBlock.data, cctx->curBlock.end); + + if (cctx->canceled) { + RMF_resetIncompleteBuild(cctx->matchTable); + return FL2_ERROR(canceled); + } + +#ifndef FL2_SINGLETHREAD + + mfThreads = MIN(RMF_threadCount(cctx->matchTable), mfThreads); + FL2POOL_addRange(cctx->factory, FL2_buildRadixTable, cctx, 1, mfThreads); + +#endif + + int err = RMF_buildTable(cctx->matchTable, 0, mfThreads > 1, cctx->curBlock); + +#ifndef FL2_SINGLETHREAD + + FL2POOL_waitAll(cctx->factory, 0); + + if (err) + return FL2_ERROR(canceled); + +#ifdef RMF_CHECK_INTEGRITY + err = RMF_integrityCheck(cctx->matchTable, cctx->curBlock.data, cctx->curBlock.start, cctx->curBlock.end, cctx->params.rParams.depth); + if (err) + return FL2_ERROR(internal); +#endif + + FL2POOL_addRange(cctx->factory, FL2_compressRadixChunk, cctx, 1, nbThreads); + + cctx->jobs[0].cSize = LZMA2_encode(cctx->jobs[0].enc, cctx->matchTable, + cctx->jobs[0].block, + &cctx->params.cParams, streamProp, + &cctx->progressIn, &cctx->progressOut, &cctx->canceled); + + FL2POOL_waitAll(cctx->factory, 0); + +#else /* FL2_SINGLETHREAD */ + + if (err) + return FL2_ERROR(canceled); + +#ifdef RMF_CHECK_INTEGRITY + err = RMF_integrityCheck(cctx->matchTable, cctx->curBlock.data, cctx->curBlock.start, cctx->curBlock.end, cctx->params.rParams.depth); + if (err) + return FL2_ERROR(internal); +#endif + cctx->jobs[0].cSize = LZMA2_encode(cctx->jobs[0].enc, cctx->matchTable, + cctx->jobs[0].block, + &cctx->params.cParams, streamProp, + &cctx->progressIn, &cctx->progressOut, &cctx->canceled); + +#endif + + for (size_t u = 0; u < nbThreads; ++u) + if (FL2_isError(cctx->jobs[u].cSize)) + return cctx->jobs[u].cSize; + + cctx->threadCount = nbThreads; + + return FL2_error_no_error; +} + +/* FL2_compressCurBlock_async() : FL2POOL_function type */ +static void FL2_compressCurBlock_async(void* const jobDescription, ptrdiff_t const n) +{ + FL2_CCtx* const cctx = (FL2_CCtx*)jobDescription; + + cctx->asyncRes = FL2_compressCurBlock_blocking(cctx, (int)n); +} + +/* FL2_compressCurBlock() : + * Update total input size. + * Clear the compressed data buffers. + * Init progress info. + * Start compression of cctx->curBlock, and wait for completion if no async compression thread exists. + */ +static size_t FL2_compressCurBlock(FL2_CCtx* const cctx, int const streamProp) +{ + FL2_initProgress(cctx); + + if (cctx->curBlock.start == cctx->curBlock.end) + return FL2_error_no_error; + + /* update largest dict size used */ + cctx->dictMax = MAX(cctx->dictMax, cctx->curBlock.end); + + cctx->outThread = 0; + cctx->threadCount = 0; + cctx->outPos = 0; + + U32 rmfWeight = ZSTD_highbit32((U32)cctx->curBlock.end); + U32 depthWeight = 2 + (cctx->params.rParams.depth >= 12) + (cctx->params.rParams.depth >= 28); + U32 encWeight; + + if (rmfWeight >= 20) { + rmfWeight = depthWeight * (rmfWeight - 10) + (rmfWeight - 19) * 12; + if (cctx->params.cParams.strategy == 0) + encWeight = 20; + else if (cctx->params.cParams.strategy == 1) + encWeight = 50; + else + encWeight = 60 + cctx->params.cParams.second_dict_bits + ZSTD_highbit32(cctx->params.cParams.fast_length) * 3U; + rmfWeight = (rmfWeight << 4) / (rmfWeight + encWeight); + encWeight = 16 - rmfWeight; + } + else { + rmfWeight = 8; + encWeight = 8; + } + + cctx->rmfWeight = rmfWeight; + cctx->encWeight = encWeight; + +#ifndef FL2_SINGLETHREAD + if(cctx->compressThread != NULL) + FL2POOL_add(cctx->compressThread, FL2_compressCurBlock_async, cctx, streamProp); + else +#endif + cctx->asyncRes = FL2_compressCurBlock_blocking(cctx, streamProp); + + return cctx->asyncRes; +} + +/* FL2_getProp() : + * Get the LZMA2 dictionary size property byte. If xxhash is enabled, includes the xxhash flag bit. + */ +static BYTE FL2_getProp(FL2_CCtx* const cctx, size_t const dictionarySize) +{ +#ifndef NO_XXHASH + return LZMA2_getDictSizeProp(dictionarySize) | (BYTE)((cctx->params.doXXH != 0) << FL2_PROP_HASH_BIT); +#else + (void)cctx; + return LZMA2_getDictSizeProp(dictionarySize); +#endif +} + +static void FL2_preBeginFrame(FL2_CCtx* const cctx, size_t const dictReduce) +{ + /* Free unsuitable match table before reallocating anything else */ + if (cctx->matchTable && !RMF_compatibleParameters(cctx->matchTable, &cctx->params.rParams, dictReduce)) { + RMF_freeMatchTable(cctx->matchTable); + cctx->matchTable = NULL; + } +} + +static size_t FL2_beginFrame(FL2_CCtx* const cctx, size_t const dictReduce) +{ + if (FL2_initEncoders(cctx) != 0) /* Create hash objects together, leaving the (large) match table last */ + return FL2_ERROR(memory_allocation); + + if (cctx->matchTable == NULL) { + cctx->matchTable = RMF_createMatchTable(&cctx->params.rParams, dictReduce, cctx->jobCount); + if (cctx->matchTable == NULL) + return FL2_ERROR(memory_allocation); + } + else { + DEBUGLOG(5, "Have compatible match table"); + RMF_applyParameters(cctx->matchTable, &cctx->params.rParams, dictReduce); + } + + cctx->dictMax = 0; + cctx->streamTotal = 0; + cctx->streamCsize = 0; + cctx->progressIn = 0; + cctx->progressOut = 0; + RMF_initProgress(cctx->matchTable); + cctx->asyncRes = 0; + cctx->outThread = 0; + cctx->threadCount = 0; + cctx->outPos = 0; + cctx->curBlock.start = 0; + cctx->curBlock.end = 0; + cctx->lockParams = 1; + + return FL2_error_no_error; +} + +static void FL2_endFrame(FL2_CCtx* const cctx) +{ + cctx->dictMax = 0; + cctx->asyncRes = 0; + cctx->lockParams = 0; +} + +/* Compress a memory buffer which may be larger than the dictionary. + * The property byte is written first unless the omit flag is set. + * Return: compressed size. + */ +static size_t FL2_compressBuffer(FL2_CCtx* const cctx, + const void* const src, size_t srcSize, + void* const dst, size_t dstCapacity) +{ + if (srcSize == 0) + return 0; + + BYTE* dstBuf = dst; + size_t const dictionarySize = cctx->params.rParams.dictionary_size; + size_t const blockOverlap = OVERLAP_FROM_DICT_SIZE(dictionarySize, cctx->params.rParams.overlap_fraction); + int streamProp = cctx->params.omitProp ? -1 : FL2_getProp(cctx, MIN(srcSize, dictionarySize)); + + cctx->curBlock.data = src; + cctx->curBlock.start = 0; + + size_t blockTotal = 0; + + do { + cctx->curBlock.end = cctx->curBlock.start + MIN(srcSize, dictionarySize - cctx->curBlock.start); + blockTotal += cctx->curBlock.end - cctx->curBlock.start; + + CHECK_F(FL2_compressCurBlock(cctx, streamProp)); + + streamProp = -1; + + for (size_t u = 0; u < cctx->threadCount; ++u) { + DEBUGLOG(5, "Write thread %u : %u bytes", (U32)u, (U32)cctx->jobs[u].cSize); + + if (dstCapacity < cctx->jobs[u].cSize) + return FL2_ERROR(dstSize_tooSmall); + + const BYTE* const outBuf = RMF_getTableAsOutputBuffer(cctx->matchTable, cctx->jobs[u].block.start); + memcpy(dstBuf, outBuf, cctx->jobs[u].cSize); + + dstBuf += cctx->jobs[u].cSize; + dstCapacity -= cctx->jobs[u].cSize; + } + srcSize -= cctx->curBlock.end - cctx->curBlock.start; + if (cctx->params.cParams.reset_interval + && blockTotal + MIN(dictionarySize - blockOverlap, srcSize) > dictionarySize * cctx->params.cParams.reset_interval) { + /* periodically reset the dictionary for mt decompression */ + DEBUGLOG(4, "Resetting dictionary after %u bytes", (unsigned)blockTotal); + cctx->curBlock.start = 0; + blockTotal = 0; + } + else { + cctx->curBlock.start = blockOverlap; + } + cctx->curBlock.data += cctx->curBlock.end - cctx->curBlock.start; + } while (srcSize != 0); + return dstBuf - (const BYTE*)dst; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_compressCCtx(FL2_CCtx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel) +{ + if (dstCapacity < 2U - cctx->params.omitProp) /* empty LZMA2 stream is byte sequence {0, 0} */ + return FL2_ERROR(dstSize_tooSmall); + + if (compressionLevel > 0) + FL2_CCtx_setParameter(cctx, FL2_p_compressionLevel, compressionLevel); + + DEBUGLOG(4, "FL2_compressCCtx : level %u, %u src => %u avail", cctx->params.compressionLevel, (U32)srcSize, (U32)dstCapacity); + +#ifndef FL2_SINGLETHREAD + /* No async compression for in-memory function */ + FL2POOL_free(cctx->compressThread); + cctx->compressThread = NULL; + cctx->timeout = 0; +#endif + + FL2_preBeginFrame(cctx, srcSize); + CHECK_F(FL2_beginFrame(cctx, srcSize)); + + size_t const cSize = FL2_compressBuffer(cctx, src, srcSize, dst, dstCapacity); + + if (FL2_isError(cSize)) + return cSize; + + BYTE* dstBuf = dst; + BYTE* const end = dstBuf + dstCapacity; + + dstBuf += cSize; + if(dstBuf >= end) + return FL2_ERROR(dstSize_tooSmall); + + if (cSize == 0) + *dstBuf++ = FL2_getProp(cctx, 0); + + *dstBuf++ = LZMA2_END_MARKER; + +#ifndef NO_XXHASH + if (cctx->params.doXXH && !cctx->params.omitProp) { + XXH32_canonical_t canonical; + DEBUGLOG(5, "Writing hash"); + unsigned long int size = end - dstBuf; + if(size < XXHASH_SIZEOF) + return FL2_ERROR(dstSize_tooSmall); + XXH32_canonicalFromHash(&canonical, XXH32(src, srcSize, 0)); + memcpy(dstBuf, &canonical, XXHASH_SIZEOF); + dstBuf += XXHASH_SIZEOF; + } +#endif + + FL2_endFrame(cctx); + + return dstBuf - (BYTE*)dst; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_compressMt(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel, + unsigned nbThreads) +{ + FL2_CCtx* const cctx = FL2_createCCtxMt(nbThreads); + if (cctx == NULL) + return FL2_ERROR(memory_allocation); + + size_t const cSize = FL2_compressCCtx(cctx, dst, dstCapacity, src, srcSize, compressionLevel); + + FL2_freeCCtx(cctx); + + return cSize; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_compress(void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + int compressionLevel) +{ + return FL2_compressMt(dst, dstCapacity, src, srcSize, compressionLevel, 1); +} + +FL2LIB_API BYTE FL2LIB_CALL FL2_getCCtxDictProp(FL2_CCtx* cctx) +{ + return LZMA2_getDictSizeProp(cctx->dictMax ? cctx->dictMax : cctx->params.rParams.dictionary_size); +} + +#define MAXCHECK(val,max) do { \ + if ((val)>(max)) { \ + return FL2_ERROR(parameter_outOfBound); \ +} } while(0) + +#define CLAMPCHECK(val,min,max) do { \ + if (((val)<(min)) | ((val)>(max))) { \ + return FL2_ERROR(parameter_outOfBound); \ +} } while(0) + + +FL2LIB_API size_t FL2LIB_CALL FL2_CCtx_setParameter(FL2_CCtx* cctx, FL2_cParameter param, size_t value) +{ + if (cctx->lockParams + && param != FL2_p_literalCtxBits && param != FL2_p_literalPosBits && param != FL2_p_posBits) + return FL2_ERROR(stage_wrong); + + switch (param) + { + case FL2_p_compressionLevel: + if (cctx->params.highCompression) { + CLAMPCHECK(value, 1, FL2_MAX_HIGH_CLEVEL); + FL2_fillParameters(cctx, &FL2_highCParameters[value]); + } + else { + CLAMPCHECK(value, 1, FL2_MAX_CLEVEL); + FL2_fillParameters(cctx, &FL2_defaultCParameters[value]); + } + cctx->params.compressionLevel = (unsigned)value; + break; + + case FL2_p_highCompression: + cctx->params.highCompression = value != 0; + FL2_CCtx_setParameter(cctx, FL2_p_compressionLevel, cctx->params.compressionLevel); + break; + + case FL2_p_dictionaryLog: + CLAMPCHECK(value, FL2_DICTLOG_MIN, FL2_DICTLOG_MAX); + cctx->params.rParams.dictionary_size = (size_t)1 << value; + break; + + case FL2_p_dictionarySize: + CLAMPCHECK(value, FL2_DICTSIZE_MIN, FL2_DICTSIZE_MAX); + cctx->params.rParams.dictionary_size = value; + break; + + case FL2_p_overlapFraction: + MAXCHECK(value, FL2_BLOCK_OVERLAP_MAX); + cctx->params.rParams.overlap_fraction = (unsigned)value; + break; + + case FL2_p_resetInterval: + if (value != 0) + CLAMPCHECK(value, FL2_RESET_INTERVAL_MIN, FL2_RESET_INTERVAL_MAX); + cctx->params.cParams.reset_interval = (unsigned)value; + break; + + case FL2_p_bufferResize: + MAXCHECK(value, FL2_BUFFER_RESIZE_MAX); + cctx->params.rParams.match_buffer_resize = (unsigned)value; + break; + + case FL2_p_hybridChainLog: + CLAMPCHECK(value, FL2_CHAINLOG_MIN, FL2_CHAINLOG_MAX); + cctx->params.cParams.second_dict_bits = (unsigned)value; + break; + + case FL2_p_hybridCycles: + CLAMPCHECK(value, FL2_HYBRIDCYCLES_MIN, FL2_HYBRIDCYCLES_MAX); + cctx->params.cParams.match_cycles = (unsigned)value; + break; + + case FL2_p_searchDepth: + CLAMPCHECK(value, FL2_SEARCH_DEPTH_MIN, FL2_SEARCH_DEPTH_MAX); + cctx->params.rParams.depth = (unsigned)value; + break; + + case FL2_p_fastLength: + CLAMPCHECK(value, FL2_FASTLENGTH_MIN, FL2_FASTLENGTH_MAX); + cctx->params.cParams.fast_length = (unsigned)value; + break; + + case FL2_p_divideAndConquer: + cctx->params.rParams.divide_and_conquer = value != 0; + break; + + case FL2_p_strategy: + MAXCHECK(value, (unsigned)FL2_ultra); + cctx->params.cParams.strategy = (FL2_strategy)value; + break; + + /* lc, lp, pb can be changed between encoder chunks. + * A condition where lc+lp > 4 is permitted to allow sequential setting, + * but will return an error code to alert the calling function. + * If lc+lp is still >4 when encoding begins, lc will be reduced. */ + case FL2_p_literalCtxBits: + MAXCHECK(value, FL2_LC_MAX); + cctx->params.cParams.lc = (unsigned)value; + if (value + cctx->params.cParams.lp > FL2_LCLP_MAX) + return FL2_ERROR(lclpMax_exceeded); + break; + + case FL2_p_literalPosBits: + MAXCHECK(value, FL2_LP_MAX); + cctx->params.cParams.lp = (unsigned)value; + if (cctx->params.cParams.lc + value > FL2_LCLP_MAX) + return FL2_ERROR(lclpMax_exceeded); + break; + + case FL2_p_posBits: + MAXCHECK(value, FL2_PB_MAX); + cctx->params.cParams.pb = (unsigned)value; + break; + +#ifndef NO_XXHASH + case FL2_p_doXXHash: + cctx->params.doXXH = value != 0; + break; +#endif + + case FL2_p_omitProperties: + cctx->params.omitProp = value != 0; + break; +#ifdef RMF_REFERENCE + case FL2_p_useReferenceMF: + cctx->params.rParams.use_ref_mf = value != 0; + break; +#endif + default: return FL2_ERROR(parameter_unsupported); + } + return value; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_CCtx_getParameter(FL2_CCtx* cctx, FL2_cParameter param) +{ + switch (param) + { + case FL2_p_compressionLevel: + return cctx->params.compressionLevel; + + case FL2_p_highCompression: + return cctx->params.highCompression; + + case FL2_p_dictionaryLog: { + size_t dictLog = FL2_DICTLOG_MIN; + while (((size_t)1 << dictLog) < cctx->params.rParams.dictionary_size) + ++dictLog; + return dictLog; + } + + case FL2_p_dictionarySize: + return cctx->params.rParams.dictionary_size; + + case FL2_p_overlapFraction: + return cctx->params.rParams.overlap_fraction; + + case FL2_p_resetInterval: + return cctx->params.cParams.reset_interval; + + case FL2_p_bufferResize: + return cctx->params.rParams.match_buffer_resize; + + case FL2_p_hybridChainLog: + return cctx->params.cParams.second_dict_bits; + + case FL2_p_hybridCycles: + return cctx->params.cParams.match_cycles; + + case FL2_p_literalCtxBits: + return cctx->params.cParams.lc; + + case FL2_p_literalPosBits: + return cctx->params.cParams.lp; + + case FL2_p_posBits: + return cctx->params.cParams.pb; + + case FL2_p_searchDepth: + return cctx->params.rParams.depth; + + case FL2_p_fastLength: + return cctx->params.cParams.fast_length; + + case FL2_p_divideAndConquer: + return cctx->params.rParams.divide_and_conquer; + + case FL2_p_strategy: + return (size_t)cctx->params.cParams.strategy; + +#ifndef NO_XXHASH + case FL2_p_doXXHash: + return cctx->params.doXXH; +#endif + + case FL2_p_omitProperties: + return cctx->params.omitProp; +#ifdef RMF_REFERENCE + case FL2_p_useReferenceMF: + return cctx->params.rParams.use_ref_mf; +#endif + default: return FL2_ERROR(parameter_unsupported); + } +} + +FL2LIB_API size_t FL2LIB_CALL FL2_CStream_setParameter(FL2_CStream* fcs, FL2_cParameter param, size_t value) +{ + return FL2_CCtx_setParameter(fcs, param, value); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_CStream_getParameter(FL2_CStream* fcs, FL2_cParameter param) +{ + return FL2_CCtx_getParameter(fcs, param); +} + +FL2LIB_API FL2_CStream* FL2LIB_CALL FL2_createCStream(void) +{ + return FL2_createCCtx_internal(1, 0); +} + +FL2LIB_API FL2_CStream* FL2LIB_CALL FL2_createCStreamMt(unsigned nbThreads, int dualBuffer) +{ + return FL2_createCCtx_internal(nbThreads, dualBuffer); +} + +FL2LIB_API void FL2LIB_CALL FL2_freeCStream(FL2_CStream * fcs) +{ + FL2_freeCCtx(fcs); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_initCStream(FL2_CStream* fcs, int compressionLevel) +{ + DEBUGLOG(4, "FL2_initCStream level %d", compressionLevel); + + fcs->endMarked = 0; + fcs->wroteProp = 0; + fcs->loopCount = 0; + + if(compressionLevel > 0) + FL2_CCtx_setParameter(fcs, FL2_p_compressionLevel, compressionLevel); + + DICT_buffer *const buf = &fcs->buf; + size_t const dictSize = fcs->params.rParams.dictionary_size; + + /* Free unsuitable objects before reallocating anything new */ + if (DICT_size(buf) < dictSize) + DICT_destruct(buf); + + FL2_preBeginFrame(fcs, 0); + +#ifdef NO_XXHASH + int const doHash = 0; +#else + int const doHash = (fcs->params.doXXH && !fcs->params.omitProp); +#endif + size_t dictOverlap = OVERLAP_FROM_DICT_SIZE(fcs->params.rParams.dictionary_size, fcs->params.rParams.overlap_fraction); + if (DICT_init(buf, dictSize, dictOverlap, fcs->params.cParams.reset_interval, doHash) != 0) + return FL2_ERROR(memory_allocation); + + CHECK_F(FL2_beginFrame(fcs, 0)); + + return 0; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_setCStreamTimeout(FL2_CStream * fcs, unsigned timeout) +{ +#ifndef FL2_SINGLETHREAD + if (timeout != 0) { + if (fcs->compressThread == NULL) { + fcs->compressThread = FL2POOL_create(1); + if (fcs->compressThread == NULL) + return FL2_ERROR(memory_allocation); + } + } + else if (!DICT_async(&fcs->buf) && fcs->dictMax == 0) { + /* Only free the thread if not dual buffering and compression not underway */ + FL2POOL_free(fcs->compressThread); + fcs->compressThread = NULL; + } + fcs->timeout = timeout; +#endif + return FL2_error_no_error; +} + +static size_t FL2_compressStream_internal(FL2_CStream* const fcs, int const ending) +{ + CHECK_F(FL2_waitCStream(fcs)); + + DICT_buffer *const buf = &fcs->buf; + + /* no compression can occur while compressed output exists */ + if (fcs->outThread == fcs->threadCount && DICT_hasUnprocessed(buf)) { + fcs->streamTotal += fcs->curBlock.end - fcs->curBlock.start; + + DICT_getBlock(buf, &fcs->curBlock); + + int streamProp = -1; + + if (!fcs->wroteProp && !fcs->params.omitProp) { + /* If the LZMA2 property byte is required and not already written, + * pass it to the compression function + */ + size_t dictionarySize = ending ? MAX(fcs->dictMax, fcs->curBlock.end) + : fcs->params.rParams.dictionary_size; + streamProp = FL2_getProp(fcs, dictionarySize); + DEBUGLOG(4, "Writing property byte : 0x%X", streamProp); + fcs->wroteProp = 1; + } + + CHECK_F(FL2_compressCurBlock(fcs, streamProp)); + } + return FL2_error_no_error; +} + +/* Copy the compressed output stored in the match table buffer. + * One slice exists per thread. + */ +FL2LIB_API size_t FL2LIB_CALL FL2_copyCStreamOutput(FL2_CStream* fcs, FL2_outBuffer *output) +{ + for (; fcs->outThread < fcs->threadCount; ++fcs->outThread) { + const BYTE* const outBuf = RMF_getTableAsOutputBuffer(fcs->matchTable, fcs->jobs[fcs->outThread].block.start) + fcs->outPos; + BYTE* const dstBuf = (BYTE*)output->dst + output->pos; + size_t const dstCapacity = output->size - output->pos; + size_t toWrite = fcs->jobs[fcs->outThread].cSize; + + toWrite = MIN(toWrite - fcs->outPos, dstCapacity); + + DEBUGLOG(5, "CStream : writing %u bytes", (U32)toWrite); + + memcpy(dstBuf, outBuf, toWrite); + fcs->outPos += toWrite; + output->pos += toWrite; + + /* If the slice is not flushed, the output is full */ + if (fcs->outPos < fcs->jobs[fcs->outThread].cSize) + return 1; + + fcs->outPos = 0; + } + return 0; +} + +static size_t FL2_compressStream_input(FL2_CStream* fcs, FL2_inBuffer* input) +{ + CHECK_F(fcs->asyncRes); + + DICT_buffer * const buf = &fcs->buf; + + while (input->pos < input->size) { + /* read input until the buffer(s) are full */ + if (DICT_needShift(buf)) { + /* cannot shift single dict during compression */ + if(!DICT_async(buf)) + CHECK_F(FL2_waitCStream(fcs)); + DICT_shift(buf); + } + + CHECK_F(fcs->asyncRes); + + DICT_put(buf, input); + + if (!DICT_availSpace(buf)) { + /* break if the compressor is not available */ + if (fcs->outThread < fcs->threadCount) + break; + + CHECK_F(FL2_compressStream_internal(fcs, 0)); + } + + CHECK_F(fcs->asyncRes); + } + + return FL2_error_no_error; +} + +static size_t FL2_loopCheck(FL2_CStream* fcs, int unchanged) +{ + if (unchanged) { + ++fcs->loopCount; + if (fcs->loopCount > FL2_MAX_LOOPS) { + FL2_cancelCStream(fcs); + return FL2_ERROR(buffer); + } + } + else { + fcs->loopCount = 0; + } + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_compressStream(FL2_CStream* fcs, FL2_outBuffer *output, FL2_inBuffer* input) +{ + if (!fcs->lockParams) + return FL2_ERROR(init_missing); + + size_t const prevIn = input->pos; + size_t const prevOut = (output != NULL) ? output->pos : 0; + + if (output != NULL && fcs->outThread < fcs->threadCount) + FL2_copyCStreamOutput(fcs, output); + + CHECK_F(FL2_compressStream_input(fcs, input)); + + if(output != NULL && fcs->outThread < fcs->threadCount) + FL2_copyCStreamOutput(fcs, output); + + CHECK_F(FL2_loopCheck(fcs, prevIn == input->pos && (output == NULL || prevOut == output->pos))); + + return fcs->outThread < fcs->threadCount; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_getDictionaryBuffer(FL2_CStream * fcs, FL2_dictBuffer * dict) +{ + if (!fcs->lockParams) + return FL2_ERROR(init_missing); + + CHECK_F(fcs->asyncRes); + + DICT_buffer *buf = &fcs->buf; + + if (!DICT_availSpace(buf) && DICT_hasUnprocessed(buf)) + CHECK_F(FL2_compressStream_internal(fcs, 0)); + + if (DICT_needShift(buf) && !DICT_async(buf)) + CHECK_F(FL2_waitCStream(fcs)); + + dict->size = (unsigned long)DICT_get(buf, &dict->dst); + + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_updateDictionary(FL2_CStream * fcs, size_t addedSize) +{ + if (DICT_update(&fcs->buf, addedSize)) + CHECK_F(FL2_compressStream_internal(fcs, 0)); + + return fcs->outThread < fcs->threadCount; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_getNextCompressedBuffer(FL2_CStream* fcs, FL2_cBuffer* cbuf) +{ + cbuf->src = NULL; + cbuf->size = 0; + +#ifndef FL2_SINGLETHREAD + CHECK_F(FL2_waitCStream(fcs)); +#endif + + if (fcs->outThread < fcs->threadCount) { + cbuf->src = RMF_getTableAsOutputBuffer(fcs->matchTable, fcs->jobs[fcs->outThread].block.start) + fcs->outPos; + cbuf->size = fcs->jobs[fcs->outThread].cSize - fcs->outPos; + ++fcs->outThread; + fcs->outPos = 0; + } + return cbuf->size; +} + +FL2LIB_API unsigned long long FL2LIB_CALL FL2_getCStreamProgress(const FL2_CStream * fcs, unsigned long long *outputSize) +{ + if (outputSize != NULL) + *outputSize = fcs->streamCsize + fcs->progressOut; + + U64 const encodeSize = fcs->curBlock.end - fcs->curBlock.start; + + if (fcs->progressIn == 0 && fcs->curBlock.end != 0) + return fcs->streamTotal + ((fcs->matchTable->progress * encodeSize / fcs->curBlock.end * fcs->rmfWeight) >> 4); + + return fcs->streamTotal + ((fcs->rmfWeight * encodeSize) >> 4) + ((fcs->progressIn * fcs->encWeight) >> 4); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_waitCStream(FL2_CStream * fcs) +{ +#ifndef FL2_SINGLETHREAD + if (FL2POOL_waitAll(fcs->compressThread, fcs->timeout) != 0) + return FL2_ERROR(timedOut); + CHECK_F(fcs->asyncRes); +#endif + return fcs->outThread < fcs->threadCount; +} + +FL2LIB_API void FL2LIB_CALL FL2_cancelCStream(FL2_CStream *fcs) +{ +#ifndef FL2_SINGLETHREAD + if (fcs->compressThread != NULL) { + fcs->canceled = 1; + + RMF_cancelBuild(fcs->matchTable); + FL2POOL_waitAll(fcs->compressThread, 0); + + fcs->canceled = 0; + } +#endif + FL2_endFrame(fcs); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_remainingOutputSize(const FL2_CStream* fcs) +{ + CHECK_F(fcs->asyncRes); + + size_t cSize = 0; + for (size_t u = fcs->outThread; u < fcs->threadCount; ++u) + cSize += fcs->jobs[u].cSize; + + return cSize; +} + +/* Write the properties byte (if required), the hash and the end marker + * into the output buffer. + */ +static void FL2_writeEnd(FL2_CStream* const fcs) +{ + size_t thread = fcs->threadCount - 1; + if (fcs->outThread == fcs->threadCount) { + fcs->outThread = 0; + fcs->threadCount = 1; + fcs->jobs[0].cSize = 0; + thread = 0; + } + BYTE *const dst = RMF_getTableAsOutputBuffer(fcs->matchTable, fcs->jobs[thread].block.start) + + fcs->jobs[thread].cSize; + + size_t pos = 0; + + if (!fcs->wroteProp && !fcs->params.omitProp) { + /* no compression occurred */ + dst[pos] = FL2_getProp(fcs, 0); + DEBUGLOG(4, "Writing property byte : 0x%X", dst[pos]); + ++pos; + fcs->wroteProp = 1; + } + + DEBUGLOG(4, "Writing end marker"); + dst[pos++] = LZMA2_END_MARKER; + +#ifndef NO_XXHASH + if (fcs->params.doXXH && !fcs->params.omitProp) { + XXH32_canonical_t canonical; + + XXH32_canonicalFromHash(&canonical, DICT_getDigest(&fcs->buf)); + DEBUGLOG(4, "Writing XXH32"); + memcpy(dst + pos, &canonical, XXHASH_SIZEOF); + + pos += XXHASH_SIZEOF; + } +#endif + fcs->jobs[thread].cSize += pos; + fcs->endMarked = 1; + + FL2_endFrame(fcs); +} + +static size_t FL2_flushStream_internal(FL2_CStream* fcs, int const ending) +{ + CHECK_F(fcs->asyncRes); + + DEBUGLOG(4, "FL2_flushStream_internal : %u to compress, %u to write", + (U32)(fcs->buf.end - fcs->buf.start), + (U32)FL2_remainingOutputSize(fcs)); + + CHECK_F(FL2_compressStream_internal(fcs, ending)); + + return fcs->outThread < fcs->threadCount; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_flushStream(FL2_CStream* fcs, FL2_outBuffer *output) +{ + if (!fcs->lockParams) + return FL2_ERROR(init_missing); + + size_t const prevOut = (output != NULL) ? output->pos : 0; + + if (output != NULL && fcs->outThread < fcs->threadCount) + FL2_copyCStreamOutput(fcs, output); + + size_t res = FL2_flushStream_internal(fcs, 0); + CHECK_F(res); + + if (output != NULL && res != 0) { + FL2_copyCStreamOutput(fcs, output); + res = fcs->outThread < fcs->threadCount; + } + + CHECK_F(FL2_loopCheck(fcs, output != NULL && prevOut == output->pos)); + + return res; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_endStream(FL2_CStream* fcs, FL2_outBuffer *output) +{ + if (!fcs->endMarked && !fcs->lockParams) + return FL2_ERROR(init_missing); + + size_t const prevOut = (output != NULL) ? output->pos : 0; + + if (output != NULL && fcs->outThread < fcs->threadCount) + FL2_copyCStreamOutput(fcs, output); + + CHECK_F(FL2_flushStream_internal(fcs, 1)); + + size_t res = FL2_waitCStream(fcs); + CHECK_F(res); + + if (!fcs->endMarked && !DICT_hasUnprocessed(&fcs->buf)) { + FL2_writeEnd(fcs); + res = 1; + } + + if (output != NULL && res != 0) { + FL2_copyCStreamOutput(fcs, output); + res = fcs->outThread < fcs->threadCount || DICT_hasUnprocessed(&fcs->buf); + } + + CHECK_F(FL2_loopCheck(fcs, output != NULL && prevOut == output->pos)); + + return res; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_getLevelParameters(int compressionLevel, int high, FL2_compressionParameters * params) +{ + if (high) { + if (compressionLevel < 0 || compressionLevel > FL2_MAX_HIGH_CLEVEL) + return FL2_ERROR(parameter_outOfBound); + *params = FL2_highCParameters[compressionLevel]; + } + else { + if (compressionLevel < 0 || compressionLevel > FL2_MAX_CLEVEL) + return FL2_ERROR(parameter_outOfBound); + *params = FL2_defaultCParameters[compressionLevel]; + } + return FL2_error_no_error; +} + +static size_t FL2_memoryUsage_internal(size_t const dictionarySize, unsigned const bufferResize, + unsigned const chainLog, + FL2_strategy const strategy, + unsigned const nbThreads) +{ + return RMF_memoryUsage(dictionarySize, bufferResize, nbThreads) + + LZMA2_encMemoryUsage(chainLog, strategy, nbThreads); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize(int compressionLevel, unsigned nbThreads) +{ + if (compressionLevel == 0) + compressionLevel = FL2_CLEVEL_DEFAULT; + + CLAMPCHECK(compressionLevel, 1, FL2_MAX_CLEVEL); + + return FL2_estimateCCtxSize_byParams(FL2_defaultCParameters + compressionLevel, nbThreads); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize_byParams(const FL2_compressionParameters * params, unsigned nbThreads) +{ + nbThreads = FL2_checkNbThreads(nbThreads); + return FL2_memoryUsage_internal(params->dictionarySize, + FL2_BUFFER_RESIZE_DEFAULT, + params->chainLog, + params->strategy, + nbThreads); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCCtxSize_usingCCtx(const FL2_CCtx * cctx) +{ + return FL2_memoryUsage_internal(cctx->params.rParams.dictionary_size, + cctx->params.rParams.match_buffer_resize, + cctx->params.cParams.second_dict_bits, + cctx->params.cParams.strategy, + cctx->jobCount) + DICT_memUsage(&cctx->buf); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize(int compressionLevel, unsigned nbThreads, int dualBuffer) +{ + return FL2_estimateCCtxSize(compressionLevel, nbThreads) + + (FL2_defaultCParameters[compressionLevel].dictionarySize << (dualBuffer != 0)); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize_byParams(const FL2_compressionParameters * params, unsigned nbThreads, int dualBuffer) +{ + return FL2_estimateCCtxSize_byParams(params, nbThreads) + + (params->dictionarySize << (dualBuffer != 0)); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateCStreamSize_usingCStream(const FL2_CStream* fcs) +{ + return FL2_estimateCCtxSize_usingCCtx(fcs); +} diff --git a/builtins/flzma2/fl2_compress_internal.h b/builtins/flzma2/fl2_compress_internal.h new file mode 100644 index 0000000000000..166457eabab7d --- /dev/null +++ b/builtins/flzma2/fl2_compress_internal.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, Conor McCarthy + * All rights reserved. + * Parts based on zstd_compress_internal.h copyright Yann Collet + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef FL2_COMPRESS_H +#define FL2_COMPRESS_H + +/*-************************************* +* Dependencies +***************************************/ +#include "mem.h" +#include "data_block.h" +#include "radix_internal.h" +#include "lzma2_enc.h" +#include "fast-lzma2.h" +#include "fl2_threading.h" +#include "fl2_pool.h" +#include "dict_buffer.h" +#ifndef NO_XXHASH +# include "xxhash.h" +#endif + +#if defined (__cplusplus) +extern "C" { +#endif + +/*-************************************* +* Context memory management +***************************************/ + +typedef struct { + FL2_lzma2Parameters cParams; + RMF_parameters rParams; + unsigned compressionLevel; + BYTE highCompression; +#ifndef NO_XXHASH + BYTE doXXH; +#endif + BYTE omitProp; +} FL2_CCtx_params; + +typedef struct { + FL2_CCtx* cctx; + LZMA2_ECtx* enc; + FL2_dataBlock block; + size_t cSize; +} FL2_job; + +struct FL2_CCtx_s { + DICT_buffer buf; + FL2_CCtx_params params; +#ifndef FL2_SINGLETHREAD + FL2POOL_ctx* factory; + FL2POOL_ctx* compressThread; +#endif + FL2_dataBlock curBlock; + size_t asyncRes; + size_t threadCount; + size_t outThread; + size_t outPos; + size_t dictMax; + U64 streamTotal; + U64 streamCsize; + FL2_matchTable* matchTable; +#ifndef FL2_SINGLETHREAD + U32 timeout; +#endif + U32 rmfWeight; + U32 encWeight; + FL2_atomic progressIn; + FL2_atomic progressOut; + int canceled; + BYTE wroteProp; + BYTE endMarked; + BYTE loopCount; + BYTE lockParams; + unsigned jobCount; + FL2_job jobs[1]; +}; + +#if defined (__cplusplus) +} +#endif + + +#endif /* FL2_COMPRESS_H */ diff --git a/builtins/flzma2/fl2_decompress.c b/builtins/flzma2/fl2_decompress.c new file mode 100644 index 0000000000000..048b03c948bad --- /dev/null +++ b/builtins/flzma2/fl2_decompress.c @@ -0,0 +1,1332 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* Parts based on zstd_decompress.c copyright Yann Collet +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include +#include "fast-lzma2.h" +#include "fl2_errors.h" +#include "fl2_internal.h" +#include "mem.h" +#include "util.h" +#include "lzma2_dec.h" +#include "fl2_threading.h" +#include "fl2_pool.h" +#include "atomic.h" +#ifndef NO_XXHASH +# include "xxhash.h" +#endif + + +#define LZMA2_PROP_UNINITIALIZED 0xFF + + +FL2LIB_API unsigned long long FL2LIB_CALL FL2_findDecompressedSize(const void *src, size_t srcSize) +{ + return LZMA2_getUnpackSize(src, srcSize); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_getDictSizeFromProp(unsigned char prop) +{ + return LZMA2_getDictSizeFromProp(prop); +} + +typedef struct +{ + LZMA2_DCtx* dec; + const void *src; + size_t packPos; + size_t packSize; + size_t unpackPos; + size_t unpackSize; + size_t res; + LZMA2_finishMode finish; +} FL2_blockDecMt; + +struct FL2_DCtx_s +{ + LZMA2_DCtx dec; +#ifndef FL2_SINGLETHREAD + FL2_blockDecMt *blocks; + FL2POOL_ctx *factory; + size_t nbThreads; +#endif + BYTE lzma2prop; +}; + +FL2LIB_API size_t FL2LIB_CALL FL2_decompress(void* dst, size_t dstCapacity, + const void* src, size_t compressedSize) +{ + return FL2_decompressMt(dst, dstCapacity, src, compressedSize, 1); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_decompressMt(void* dst, size_t dstCapacity, + const void* src, size_t compressedSize, + unsigned nbThreads) +{ + FL2_DCtx* const dctx = FL2_createDCtxMt(nbThreads); + if(dctx == NULL) + return FL2_ERROR(memory_allocation); + + size_t const dSize = FL2_decompressDCtx(dctx, + dst, dstCapacity, + src, compressedSize); + + FL2_freeDCtx(dctx); + + return dSize; +} + +FL2LIB_API FL2_DCtx* FL2LIB_CALL FL2_createDCtx(void) +{ + return FL2_createDCtxMt(1); +} + +FL2LIB_API FL2_DCtx *FL2LIB_CALL FL2_createDCtxMt(unsigned nbThreads) +{ + DEBUGLOG(3, "FL2_createDCtx"); + + FL2_DCtx* const dctx = malloc(sizeof(FL2_DCtx)); + + if (dctx == NULL) + return NULL; + + LZMA_constructDCtx(&dctx->dec); + + dctx->lzma2prop = LZMA2_PROP_UNINITIALIZED; + + nbThreads = FL2_checkNbThreads(nbThreads); + +#ifndef FL2_SINGLETHREAD + dctx->nbThreads = 1; + dctx->blocks = NULL; + dctx->factory = NULL; + + if (nbThreads > 1) { + dctx->blocks = malloc(nbThreads * sizeof(FL2_blockDecMt)); + dctx->factory = FL2POOL_create(nbThreads - 1); + + if (dctx->blocks == NULL || dctx->factory == NULL) { + FL2_freeDCtx(dctx); + return NULL; + } + dctx->blocks[0].dec = &dctx->dec; + + for (; dctx->nbThreads < nbThreads; ++dctx->nbThreads) { + + dctx->blocks[dctx->nbThreads].dec = malloc(sizeof(LZMA2_DCtx)); + + if (dctx->blocks[dctx->nbThreads].dec == NULL) { + FL2_freeDCtx(dctx); + return NULL; + } + LZMA_constructDCtx(dctx->blocks[dctx->nbThreads].dec); + } + } +#endif + + return dctx; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_freeDCtx(FL2_DCtx* dctx) +{ + if (dctx == NULL) + return FL2_error_no_error; + + DEBUGLOG(3, "FL2_freeDCtx"); + + LZMA_destructDCtx(&dctx->dec); + +#ifndef FL2_SINGLETHREAD + if (dctx->blocks != NULL) { + for (unsigned thread = 1; thread < dctx->nbThreads; ++thread) { + LZMA_destructDCtx(dctx->blocks[thread].dec); + free(dctx->blocks[thread].dec); + } + free(dctx->blocks); + } + FL2POOL_free(dctx->factory); +#endif + free(dctx); + + return FL2_error_no_error; +} + +#ifndef FL2_SINGLETHREAD + +FL2LIB_API unsigned FL2LIB_CALL FL2_getDCtxThreadCount(const FL2_DCtx * dctx) +{ + return (unsigned)dctx->nbThreads; +} + +/* FL2_decompressCtxBlock() : FL2POOL_function type */ +static void FL2_decompressCtxBlock(void* const jobDescription, ptrdiff_t const n) +{ + FL2_blockDecMt* const blocks = (FL2_blockDecMt*)jobDescription; + size_t srcLen = blocks[n].packSize; + + DEBUGLOG(4, "Thread %u: decoding block of input size %u, output size %u", (unsigned)n, (unsigned)srcLen, (unsigned)blocks[n].unpackSize); + + blocks[n].res = LZMA2_decodeToDic(blocks[n].dec, blocks[n].unpackSize, blocks[n].src, &srcLen, blocks[n].finish); + + /* If no error occurred, store into res the dic_pos value, which is the end of the decompressed data in the buffer */ + if (!FL2_isError(blocks[n].res)) + blocks[n].res = blocks[n].dec->dic_pos; +} + +static size_t FL2_decompressCtxBlocksMt(FL2_DCtx* const dctx, const BYTE *const src, BYTE *const dst, size_t const dstCapacity, size_t const nbThreads) +{ + FL2_blockDecMt* const blocks = dctx->blocks; + + /* Initial check for block 0. The others are uncalculated */ + if (dstCapacity < blocks[0].unpackSize) + return FL2_ERROR(dstSize_tooSmall); + + blocks[0].packPos = 0; + blocks[0].unpackPos = 0; + blocks[0].src = src; + + BYTE const prop = dctx->lzma2prop & FL2_LZMA_PROP_MASK; + + for (size_t thread = 1; thread < nbThreads; ++thread) { + blocks[thread].packPos = blocks[thread - 1].packPos + blocks[thread - 1].packSize; + blocks[thread].unpackPos = blocks[thread - 1].unpackPos + blocks[thread - 1].unpackSize; + blocks[thread].src = src + blocks[thread].packPos; + CHECK_F(LZMA2_initDecoder(blocks[thread].dec, prop, dst + blocks[thread].unpackPos, blocks[thread].unpackSize)); + } + if (dstCapacity < blocks[nbThreads - 1].unpackPos + blocks[nbThreads - 1].unpackSize) + return FL2_ERROR(dstSize_tooSmall); + + /* Decompress thread 1..n */ + FL2POOL_addRange(dctx->factory, FL2_decompressCtxBlock, blocks, 1, nbThreads); + + /* Decompress thread 0 */ + CHECK_F(LZMA2_initDecoder(blocks[0].dec, prop, dst + blocks[0].unpackPos, blocks[0].unpackSize)); + FL2_decompressCtxBlock(blocks, 0); + + FL2POOL_waitAll(dctx->factory, 0); + + size_t dSize = 0; + for (size_t thread = 0; thread < nbThreads; ++thread) { + if (FL2_isError(blocks[thread].res)) + return blocks[thread].res; + dSize += blocks[thread].res; + } + return dSize; +} + +static void FL2_resetMtBlocks(FL2_DCtx* const dctx) +{ + for (size_t thread = 0; thread < dctx->nbThreads; ++thread) { + dctx->blocks[thread].finish = LZMA_FINISH_ANY; + dctx->blocks[thread].packSize = 0; + dctx->blocks[thread].unpackSize = 0; + } +} + +/* Decompress an entire stream stored in memory */ +static size_t FL2_decompressDCtxMt(FL2_DCtx* const dctx, + void* dst, size_t dstCapacity, + const void* src, size_t *const srcLen) +{ + size_t srcSize = *srcLen; + *srcLen = 0; + + FL2_resetMtBlocks(dctx); + + size_t unpackSize = 0; + FL2_blockDecMt* const blocks = dctx->blocks; + size_t thread = 0; + size_t pos = 0; + while (pos < srcSize) { + LZMA2_chunk inf; + int type = LZMA2_parseInput(src, pos, srcSize - pos, &inf); + + /* All src data must be in memory so CHUNK_MORE_DATA is an error */ + if (type == CHUNK_ERROR || type == CHUNK_MORE_DATA) + return FL2_ERROR(corruption_detected); + + /* CHUNK_DICT_RESET is used to signal block completion except for pos 0 */ + if (pos == 0 && type == CHUNK_DICT_RESET) + type = CHUNK_CONTINUE; + + if (type == CHUNK_DICT_RESET || type == CHUNK_FINAL) { + if (type == CHUNK_FINAL) { + /* The finish value will be passed to the decoder */ + blocks[thread].finish = LZMA_FINISH_END; + /* CHUNK_FINAL means a single 0 byte */ + assert(inf.pack_size == 1); + ++blocks[thread].packSize; + } + /* Move to the next thread. Decoding will begin if all threads are used. */ + ++thread; + } + if (type == CHUNK_FINAL || (type == CHUNK_DICT_RESET && thread == dctx->nbThreads)) { + size_t res = FL2_decompressCtxBlocksMt(dctx, (BYTE*)src, dst, dstCapacity, thread); + if (FL2_isError(res)) + return res; + + unpackSize += res; + /* Store the unpack size in decoder 0 where it would be in single thread */ + dctx->dec.dic_pos = unpackSize; + /* Input used is the end of data consumed by the last thread */ + *srcLen += blocks[thread - 1].packPos + blocks[thread - 1].packSize; + + if (type == CHUNK_FINAL) + return LZMA_STATUS_FINISHED; + + /* Only excecuted at a dict reset. pos is the location of the reset */ + src = (BYTE*)src + pos; + srcSize -= pos; + dst = (BYTE*)dst + res; + dstCapacity -= res; + pos = 0; + thread = 0; + + FL2_resetMtBlocks(dctx); + } + else { + /* Not the end or a dict reset, so add it to the current block */ + blocks[thread].packSize += inf.pack_size; + blocks[thread].unpackSize += inf.unpack_size; + pos += inf.pack_size; + } + } + return FL2_ERROR(srcSize_wrong); +} + +#endif + +FL2LIB_API size_t FL2LIB_CALL FL2_initDCtx(FL2_DCtx * dctx, unsigned char prop) +{ + if((prop & FL2_LZMA_PROP_MASK) > 40) + return FL2_ERROR(corruption_detected); + + dctx->lzma2prop = prop; + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_decompressDCtx(FL2_DCtx* dctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize) +{ + BYTE prop = dctx->lzma2prop; + const BYTE *srcBuf = src; + + if (prop == LZMA2_PROP_UNINITIALIZED) { + prop = *(const BYTE*)src; + ++srcBuf; + --srcSize; + } + +#ifndef NO_XXHASH + BYTE const doHash = prop >> FL2_PROP_HASH_BIT; +#endif + prop &= FL2_LZMA_PROP_MASK; + + DEBUGLOG(4, "FL2_decompressDCtx : dict prop 0x%X, do hash %u", prop, doHash); + + size_t srcPos = srcSize; + + size_t dicPos = 0; + size_t res; +#ifndef FL2_SINGLETHREAD + if (dctx->blocks != NULL) { + dctx->lzma2prop = prop; + res = FL2_decompressDCtxMt(dctx, dst, dstCapacity, srcBuf, &srcPos); + } + else +#endif + { + CHECK_F(LZMA2_initDecoder(&dctx->dec, prop, dst, dstCapacity)); + + dicPos = dctx->dec.dic_pos; + + res = LZMA2_decodeToDic(&dctx->dec, dstCapacity, srcBuf, &srcPos, LZMA_FINISH_END); + } + + dctx->lzma2prop = LZMA2_PROP_UNINITIALIZED; + + if (FL2_isError(res)) + return res; + /* All src data must be in memory */ + if (res == LZMA_STATUS_NEEDS_MORE_INPUT) + return FL2_ERROR(srcSize_wrong); + + dicPos = dctx->dec.dic_pos - dicPos; + +#ifndef NO_XXHASH + if (doHash) { + XXH32_canonical_t canonical; + U32 hash; + + DEBUGLOG(4, "Checking hash"); + + if (srcSize - srcPos < XXHASH_SIZEOF) + return FL2_ERROR(srcSize_wrong); + + memcpy(&canonical, srcBuf + srcPos, XXHASH_SIZEOF); + hash = XXH32_hashFromCanonical(&canonical); + if (hash != XXH32(dst, dicPos, 0)) + return FL2_ERROR(checksum_wrong); + } +#endif + return dicPos; +} + +/*===== Streaming decompression functions =====*/ + +typedef enum +{ + FL2DEC_STAGE_INIT, + FL2DEC_STAGE_DECOMP, +#ifndef FL2_SINGLETHREAD + FL2DEC_STAGE_MT_WRITE, +#endif + FL2DEC_STAGE_HASH, + FL2DEC_STAGE_FINISHED +} FL2_decStage; + +#ifndef FL2_SINGLETHREAD +typedef struct FL2_decInbuf_s FL2_decInbuf; + +struct FL2_decInbuf_s +{ + FL2_decInbuf *next; + size_t length; + BYTE inBuf[1]; +}; + +typedef struct +{ + FL2_decInbuf *first; + FL2_decInbuf *last; + size_t startPos; + size_t endPos; + size_t unpackSize; +} FL2_decBlock; + +typedef struct +{ + LZMA2_DCtx dec; + FL2_decBlock inBlock; + BYTE *outBuf; + size_t bufSize; + size_t res; +} FL2_decJob; + +typedef struct +{ + FL2POOL_ctx* factory; + FL2_decInbuf *head; + FL2_decInbuf *cur; + size_t curPos; + size_t numThreads; + size_t maxThreads; + size_t srcThread; + size_t srcPos; + size_t memTotal; + size_t memLimit; + BYTE isFinal; + BYTE failState; + BYTE canceled; + BYTE prop; +#ifndef NO_XXHASH + XXH32_canonical_t hash; +#endif + FL2_decJob threads[1]; +} FL2_decMt; +#endif + +#define LZMA_OVERLAP_SIZE (LZMA_REQUIRED_INPUT_MAX * 2) + +struct FL2_DStream_s +{ +#ifndef FL2_SINGLETHREAD + FL2_decMt *decmt; + FL2POOL_ctx* decompressThread; +#endif + LZMA2_DCtx dec; + FL2_outBuffer* asyncOutput; + FL2_inBuffer* asyncInput; + size_t asyncRes; + U64 streamTotal; + size_t overlapSize; + FL2_atomic progress; + unsigned timeout; +#ifndef NO_XXHASH + XXH32_state_t *xxh; + XXH32_canonical_t xxhIn; + size_t xxhPos; +#endif + FL2_decStage stage; + BYTE doHash; + BYTE loopCount; + BYTE wait; + BYTE overlap[LZMA_OVERLAP_SIZE]; +}; + +static size_t FL2_decompressInput(FL2_DStream* fds, FL2_outBuffer* output, FL2_inBuffer* input) +{ + if (fds->stage == FL2DEC_STAGE_DECOMP) { + size_t destSize = output->size - output->pos; + size_t srcSize = input->size - input->pos; + size_t const res = LZMA2_decodeToBuf(&fds->dec, (BYTE*)output->dst + output->pos, &destSize, (const BYTE*)input->src + input->pos, &srcSize, LZMA_FINISH_ANY); + + DEBUGLOG(5, "Decoded %u bytes", (U32)destSize); + +#ifndef NO_XXHASH + if (fds->doHash) + XXH32_update(fds->xxh, (BYTE*)output->dst + output->pos, destSize); +#endif + FL2_atomic_add(fds->progress, (long)destSize); + + output->pos += destSize; + input->pos += srcSize; + + if (FL2_isError(res)) + return res; + if (res == LZMA_STATUS_FINISHED) { + DEBUGLOG(4, "Found end mark"); + fds->stage = fds->doHash ? FL2DEC_STAGE_HASH : FL2DEC_STAGE_FINISHED; + } + } + return FL2_error_no_error; +} + +static size_t FL2_decompressOverlappedInput(FL2_DStream* fds, FL2_outBuffer* output, FL2_inBuffer* input) +{ + if (fds->overlapSize != 0) { + size_t toRead = MIN(input->size - input->pos, LZMA_OVERLAP_SIZE - fds->overlapSize); + memcpy(fds->overlap + fds->overlapSize, (BYTE*)input->src + input->pos, toRead); + FL2_inBuffer temp = { fds->overlap, fds->overlapSize + toRead, 0 }; + CHECK_F(FL2_decompressInput(fds, output, &temp)); + if (temp.pos >= fds->overlapSize) { + input->pos += temp.pos - fds->overlapSize; + fds->overlapSize = 0; + } + else { + fds->overlapSize -= temp.pos; + memmove(fds->overlap, fds->overlap + temp.pos, fds->overlapSize); + } + } + if(input->pos == input->size) + return FL2_error_no_error; + + if(fds->overlapSize == 0) + CHECK_F(FL2_decompressInput(fds, output, input)); + + size_t toRead = input->size - input->pos; + /* More input needed if not finished, output not full and input is below minimum. + * Safe to take all input because stream will be beyond decomp stage if the terminator is present. */ + if (fds->stage == FL2DEC_STAGE_DECOMP && output->pos < output->size && toRead <= LZMA_REQUIRED_INPUT_MAX) { + toRead = MIN(toRead, LZMA_OVERLAP_SIZE - fds->overlapSize); + memcpy(fds->overlap + fds->overlapSize, (BYTE*)input->src + input->pos, toRead); + input->pos += toRead; + fds->overlapSize += toRead; + } + return FL2_error_no_error; +} + +#ifndef FL2_SINGLETHREAD + +/* Free buffer nodes from node to the end, except keep */ +static void LZMA2_freeInbufNodeChain(FL2_decMt *const decmt, FL2_decInbuf *node, FL2_decInbuf *const keep) +{ + while (node) { + FL2_decInbuf *const next = node->next; + if (node != keep) { + decmt->memTotal -= sizeof(FL2_decInbuf) + LZMA2_MT_INPUT_SIZE - 1; + free(node); + } + else { + node->next = NULL; + } + node = next; + } +} + +/* Free all buffer nodes except the head */ +static void LZMA2_freeExtraInbufNodes(FL2_decMt *const decmt) +{ + LZMA2_freeInbufNodeChain(decmt, decmt->head->next, NULL); + decmt->head->next = NULL; + decmt->head->length = 0; +} + +static void FL2_freeOutputBuffers(FL2_decMt *const decmt) +{ + for (size_t thread = 0; thread < decmt->maxThreads; ++thread) + if(decmt->threads[thread].outBuf != NULL) { + decmt->memTotal -= decmt->threads[thread].bufSize; + free(decmt->threads[thread].outBuf); + decmt->threads[thread].outBuf = NULL; + } + decmt->numThreads = 0; +} + +static void FL2_lzma2DecMt_cleanup(FL2_decMt *const decmt) +{ + if (decmt) { + FL2_freeOutputBuffers(decmt); + LZMA2_freeExtraInbufNodes(decmt); + } +} + +static void FL2_lzma2DecMt_free(FL2_decMt *const decmt) +{ + if (decmt) { + FL2_freeOutputBuffers(decmt); + LZMA2_freeInbufNodeChain(decmt, decmt->head, NULL); + FL2POOL_free(decmt->factory); + free(decmt); + } +} + +static void FL2_lzma2DecMt_init(FL2_decMt *const decmt) +{ + if (decmt) { + decmt->cur = NULL; + decmt->failState = 0; + decmt->isFinal = 0; + decmt->canceled = 0; + decmt->memTotal = 0; + FL2_freeOutputBuffers(decmt); + LZMA2_freeExtraInbufNodes(decmt); + decmt->threads[0].inBlock.first = decmt->head; + decmt->threads[0].inBlock.last = decmt->head; + decmt->threads[0].inBlock.startPos = 0; + decmt->threads[0].inBlock.endPos = 0; + decmt->threads[0].inBlock.unpackSize = 0; + } +} + +static int FL2_lzma2DecMt_initProp(FL2_decMt *const decmt, BYTE prop) +{ + decmt->prop = prop; + size_t const dictSize = LZMA2_getDictSizeFromProp(prop); + /* Minimum memory is for two threads, one dict size per thread plus a minimal amount of + * compressed data for each. Compression to < 1/6 is uncommon. */ + if (decmt->memLimit < (dictSize + dictSize / 6U) * 2U) { + DEBUGLOG(3, "Using ST decompression due to dict size %u, memory limit %u", (unsigned)dictSize, (unsigned)decmt->memLimit); + decmt->failState = 1; + return 1; + } + return 0; +} + +static FL2_decInbuf * FL2_createInbufNode(FL2_decMt *const decmt, FL2_decInbuf *const prev) +{ + decmt->memTotal += sizeof(FL2_decInbuf) + LZMA2_MT_INPUT_SIZE - 1; + if (decmt->memTotal > decmt->memLimit) + return NULL; + + FL2_decInbuf *const node = malloc(sizeof(FL2_decInbuf) + LZMA2_MT_INPUT_SIZE - 1); + if (node == NULL) + return NULL; + + node->next = NULL; + node->length = 0; + if (prev) { + /* Node buffers overlap by LZMA_REQUIRED_INPUT_MAX */ + memcpy(node->inBuf, prev->inBuf + prev->length - LZMA_REQUIRED_INPUT_MAX, LZMA_REQUIRED_INPUT_MAX); + prev->next = node; + node->length = LZMA_REQUIRED_INPUT_MAX; + } + return node; +} + +static FL2_decMt *FL2_lzma2DecMt_create(unsigned maxThreads) +{ + maxThreads += !maxThreads; + + FL2_decMt *const decmt = malloc(sizeof(FL2_decMt) + (maxThreads - 1) * sizeof(FL2_decJob)); + if (decmt == NULL) + return NULL; + + decmt->memTotal = 0; + decmt->memLimit = (size_t)1 << 29; + decmt->maxThreads = 0; + + /* The head always exists and is only freed on deallocation */ + decmt->head = FL2_createInbufNode(decmt, NULL); + if (decmt->head == NULL) { + free(decmt); + return NULL; + } + + decmt->factory = FL2POOL_create(maxThreads - 1); + + if (maxThreads > 1 && decmt->factory == NULL) { + FL2_lzma2DecMt_free(decmt); + return NULL; + } + decmt->numThreads = 0; + decmt->maxThreads = maxThreads; + + for (size_t n = 0; n < maxThreads; ++n) { + decmt->threads[n].outBuf = NULL; + LZMA_constructDCtx(&decmt->threads[n].dec); + } + FL2_lzma2DecMt_init(decmt); + + return decmt; +} + +/* Read chunk headers and advance inBlock->endPos to the next chunk + * until it points beyond the available data. + * Add the size of each chunk to inBlock->unpackSize + */ +static LZMA2_parseRes FL2_parseMt(FL2_decBlock* const inBlock) +{ + LZMA2_parseRes res = CHUNK_MORE_DATA; + FL2_decInbuf *const cur = inBlock->last; + if (cur == NULL) + return res; + + int first = inBlock->unpackSize == 0; + + while (inBlock->endPos < cur->length) { + LZMA2_chunk inf; + res = LZMA2_parseInput(cur->inBuf, inBlock->endPos, cur->length - inBlock->endPos, &inf); + if (first && res == CHUNK_DICT_RESET) + res = CHUNK_CONTINUE; + if (res != CHUNK_CONTINUE) + break; + + inBlock->endPos += inf.pack_size; + inBlock->unpackSize += inf.unpack_size; + + first = 0; + } + /* Skip the 1-byte end marker if found */ + inBlock->endPos += (res == CHUNK_FINAL); + return res; +} + +/* Decompress an entire block starting with a dict reset and ending with + * the last chunk before the next dict reset, or the terminator. + * The input is a chain of buffers. + */ +static size_t FL2_decompressBlockMt(FL2_DStream* const fds, size_t const thread) +{ + FL2_decMt *const decmt = fds->decmt; + FL2_decJob *const ti = &decmt->threads[thread]; + LZMA2_DCtx *const dec = &ti->dec; + + DEBUGLOG(4, "Thread %u: decoding block of size %u", (unsigned)thread, (unsigned)ti->bufSize); + + CHECK_F(LZMA2_initDecoder(dec, decmt->prop, ti->outBuf, ti->bufSize)); + + /* Input buffer node containing the starting chunk. If thread > 0 this is usually + * the last input buffer node of the previous thread. */ + FL2_decInbuf *cur = ti->inBlock.first; + /* Position of the starting chunk. */ + size_t inPos = ti->inBlock.startPos; + /* Flag to indicate this block ends with the terminator */ + BYTE const last = decmt->isFinal && (thread == decmt->numThreads - 1); + + while (!decmt->canceled) { + size_t srcSize = cur->length - inPos; + size_t const dicPos = dec->dic_pos; + + size_t const res = LZMA2_decodeToDic(dec, + ti->bufSize, + cur->inBuf + inPos, &srcSize, + last && cur == ti->inBlock.last ? LZMA_FINISH_END : LZMA_FINISH_ANY); + + CHECK_F(res); + + FL2_atomic_add(fds->progress, (long)(dec->dic_pos - dicPos)); + + if (res == LZMA_STATUS_FINISHED) + DEBUGLOG(4, "Found end mark"); + + if (cur == ti->inBlock.last) + break; + + /* Advance the position and switch to the next input buffer in the chain if necessary */ + inPos += srcSize; + if (inPos + LZMA_REQUIRED_INPUT_MAX >= cur->length) { + inPos -= cur->length - LZMA_REQUIRED_INPUT_MAX; + cur = cur->next; + } + } + + if (decmt->canceled) + return FL2_ERROR(canceled); + + return FL2_error_no_error; +} + +/* + * Write the data from the output buffer of each thread. + */ +static size_t FL2_writeStreamBlocks(FL2_DStream* const fds, FL2_outBuffer* const output) +{ + FL2_decMt *const decmt = fds->decmt; + + for (; decmt->srcThread < fds->decmt->numThreads; ++decmt->srcThread) { + FL2_decJob *thread = decmt->threads + decmt->srcThread; + size_t to_write = MIN(thread->bufSize - decmt->srcPos, output->size - output->pos); + memcpy((BYTE*)output->dst + output->pos, thread->outBuf + decmt->srcPos, to_write); + +#ifndef NO_XXHASH + if (fds->doHash) + XXH32_update(fds->xxh, (BYTE*)output->dst + output->pos, to_write); +#endif + decmt->srcPos += to_write; + output->pos += to_write; + + if (decmt->srcPos < thread->bufSize) + break; + + decmt->srcPos = 0; + } + if (decmt->srcThread < fds->decmt->numThreads) + return 0; + + FL2_freeOutputBuffers(fds->decmt); + fds->decmt->numThreads = 0; + + return 1; +} + +/* FL2_decompressBlock() : FL2POOL_function type */ +static void FL2_decompressBlock(void* const jobDescription, ptrdiff_t const n) +{ + FL2_DStream* const fds = (FL2_DStream*)jobDescription; + fds->decmt->threads[n].res = FL2_decompressBlockMt(fds, n); +} + +static size_t FL2_decompressBlocksMt(FL2_DStream* const fds) +{ + /* Set the threads to work on the blocks */ + FL2_decMt * const decmt = fds->decmt; + FL2POOL_addRange(decmt->factory, FL2_decompressBlock, fds, 1, decmt->numThreads); + + /* Do block 0 in the main thread */ + decmt->threads[0].res = FL2_decompressBlockMt(fds, 0); + FL2POOL_waitAll(fds->decmt->factory, 0); + + /* Free all input buffers except the last */ + FL2_decInbuf *const keep = decmt->threads[decmt->numThreads - 1].inBlock.last; + LZMA2_freeInbufNodeChain(decmt, decmt->head, keep); + /* The last becomes the new head */ + decmt->head = keep; + decmt->threads[0].inBlock.first = keep; + decmt->threads[0].inBlock.last = keep; + /* Initialize the start and end to the next chunk */ + decmt->threads[0].inBlock.endPos = decmt->threads[decmt->numThreads - 1].inBlock.endPos; + decmt->threads[0].inBlock.startPos = decmt->threads[0].inBlock.endPos; + decmt->threads[0].inBlock.unpackSize = 0; + + for (size_t thread = 0; thread < decmt->numThreads; ++thread) + if (FL2_isError(decmt->threads[thread].res)) + return decmt->threads[thread].res; + + decmt->srcThread = 0; + decmt->srcPos = 0; + + return FL2_error_no_error; +} + +static size_t FL2_handleFinalChunkMt(FL2_decMt *const decmt, size_t res) +{ + FL2_decBlock *inBlock = &decmt->threads[decmt->numThreads].inBlock; + + FL2_decJob * const done = decmt->threads + decmt->numThreads; + ++decmt->numThreads; + + done->bufSize = done->inBlock.unpackSize; + decmt->memTotal += done->bufSize; + if (decmt->memTotal > decmt->memLimit) + return FL2_ERROR(memory_allocation); + + /* Decompressed data will be stored in outBuf */ + done->outBuf = malloc(done->bufSize); + if (done->outBuf == NULL) + return FL2_ERROR(memory_allocation); + + decmt->isFinal = (res == CHUNK_FINAL); + + if (decmt->numThreads == decmt->maxThreads || decmt->isFinal) + return 1; + + /* Set up the start of the next series of chunks. The first buffer is the last of the those already loaded. */ + inBlock = &decmt->threads[decmt->numThreads].inBlock; + inBlock->first = done->inBlock.last; + inBlock->last = inBlock->first; + inBlock->endPos = done->inBlock.endPos; + inBlock->startPos = inBlock->endPos; + inBlock->unpackSize = 0; + + return 0; +} + +/* Read input into the buffer chain, adding new nodes when necessary. + * The chunks in each buffer are parsed before a new buffer is allocated. + * No new buffers will be allocated after the terminator is encountered. + * Returns 1 if the terminator was found or enough work exists for all threads, + * 0 if input is empty, + * or FL2_error_corruption_detected, or FL2_error_memory_allocation. + * The memory limit is enforced by returning FL2_error_memory_allocation. + */ +static size_t FL2_loadInputMt(FL2_decMt *const decmt, FL2_inBuffer* const input) +{ + FL2_decBlock *inBlock = &decmt->threads[decmt->numThreads].inBlock; + LZMA2_parseRes res = CHUNK_CONTINUE; + /* Continue while input is available or the parse pos is not beyond the end */ + while (input->pos < input->size || inBlock->endPos < inBlock->last->length) { + if (inBlock->endPos < inBlock->last->length) { + res = FL2_parseMt(inBlock); + if (res == CHUNK_ERROR) + return FL2_ERROR(corruption_detected); + + if (res == CHUNK_DICT_RESET || res == CHUNK_FINAL) { + /* We have a complete series of chunks starting from a dict reset and + * ending with another reset or the terminator. Set up the thread job. */ + size_t end = FL2_handleFinalChunkMt(decmt, res); + + /* end is nonzero if memory limit hit or ready to decode */ + if (end != 0) { + inBlock = &decmt->threads[decmt->numThreads - 1].inBlock; + /* rewind input in case data beyond terminator was read. Required for xxhash and container formats */ + size_t back = MIN(input->pos, inBlock->last->length - inBlock->endPos); + input->pos -= back; + inBlock->last->length -= back; + return end; + } + inBlock = &decmt->threads[decmt->numThreads].inBlock; + } + } + if (inBlock->last->length >= LZMA2_MT_INPUT_SIZE && inBlock->endPos + LZMA_REQUIRED_INPUT_MAX >= inBlock->last->length) { + /* Create a new buffer if endPos is within the overlap region. The function copies the overlap. */ + FL2_decInbuf *const next = FL2_createInbufNode(decmt, inBlock->last); + if (next == NULL) { + if (inBlock->endPos < inBlock->last->length) { + size_t back = MIN(input->pos, inBlock->last->length - inBlock->endPos); + input->pos -= back; + inBlock->last->length -= back; + } + return FL2_ERROR(memory_allocation); + } + inBlock->last = next; + inBlock->endPos -= LZMA2_MT_INPUT_SIZE - LZMA_REQUIRED_INPUT_MAX; + } + /* Read as much input as possible */ + size_t toread = MIN(input->size - input->pos, LZMA2_MT_INPUT_SIZE - inBlock->last->length); + memcpy(inBlock->last->inBuf + inBlock->last->length, (BYTE*)input->src + input->pos, toread); + inBlock->last->length += toread; + input->pos += toread; + + /* Do not continue if we have an incomplete chunk header */ + if (res == CHUNK_MORE_DATA && toread == 0) + break; + } + return 0; +} + +/* Handle MT buffer allocation failure. + * Decompress input from the MT buffer chain + * until it is possible to switch to the caller's input buffer + */ +static size_t FL2_decompressFailedMt(FL2_DStream* const fds, FL2_outBuffer* const output, FL2_inBuffer* const input) +{ + FL2_decMt *const decmt = fds->decmt; + + if(decmt->head->length == 0) + return FL2_decompressOverlappedInput(fds, output, input); + + if (!decmt->failState) { + /* On first call of this function, free any output buffers already allocated, + * and set up the read position in the input buffer chain. The main thread's decoder needs initialization too. */ + DEBUGLOG(3, "Switching to ST decompression. Memory: %u, limit %u", (unsigned)decmt->memTotal, (unsigned)decmt->memLimit); + + FL2_freeOutputBuffers(decmt); + + decmt->cur = decmt->threads[0].inBlock.first; + decmt->curPos = decmt->threads[0].inBlock.startPos; + + decmt->failState = 1; + + CHECK_F(LZMA2_initDecoder(&fds->dec, decmt->prop, NULL, 0)); + } + FL2_decInbuf *const cur = decmt->cur; + + FL2_inBuffer temp; + temp.src = cur->inBuf; + temp.pos = decmt->curPos; + temp.size = cur->length; + + CHECK_F(FL2_decompressInput(fds, output, &temp)); + + decmt->curPos = temp.pos; + + if (temp.pos + LZMA_REQUIRED_INPUT_MAX >= temp.size) { + if (cur->next == NULL) { + /* The last buffer in the chain */ + fds->overlapSize = temp.size - temp.pos; + memcpy(fds->overlap, cur->inBuf + temp.pos, fds->overlapSize); + decmt->cur = NULL; + LZMA2_freeExtraInbufNodes(decmt); + } + else { + decmt->curPos -= cur->length - LZMA_REQUIRED_INPUT_MAX; + decmt->cur = cur->next; + } + } + + return FL2_error_no_error; +} + +static size_t FL2_decompressStreamMt(FL2_DStream* const fds, FL2_outBuffer* const output, FL2_inBuffer* const input) +{ + FL2_decMt *const decmt = fds->decmt; + + /* failState is set if the memory limit was hit or allocation failed */ + if(decmt->failState) + return FL2_decompressFailedMt(fds, output, input); + + if (fds->stage == FL2DEC_STAGE_DECOMP) { + /* Allocate and fill the input buffer chain */ + size_t const res = FL2_loadInputMt(decmt, input); + + /* Failover if allocation failed */ + if (FL2_getErrorCode(res) == FL2_error_memory_allocation) + return FL2_decompressFailedMt(fds, output, input); + CHECK_F(res); + + /* res > 0 means all threads have input or the terminator was encountered */ + if (res > 0) { + CHECK_F(FL2_decompressBlocksMt(fds)); + fds->stage = FL2DEC_STAGE_MT_WRITE; + } + } + if (fds->stage == FL2DEC_STAGE_MT_WRITE) { + if (FL2_writeStreamBlocks(fds, output)) + fds->stage = decmt->isFinal ? (fds->doHash ? FL2DEC_STAGE_HASH : FL2DEC_STAGE_FINISHED) + : FL2DEC_STAGE_DECOMP; + } + return fds->stage != FL2DEC_STAGE_FINISHED; +} + +#endif /* FL2_SINGLETHREAD */ + +FL2LIB_API FL2_DStream* FL2LIB_CALL FL2_createDStream(void) +{ + return FL2_createDStreamMt(1); +} + +static void FL2_resetDStream(FL2_DStream *fds) +{ + fds->stage = FL2DEC_STAGE_INIT; + fds->asyncRes = 0; + fds->streamTotal = 0; + fds->overlapSize = 0; + fds->progress = 0; +#ifndef NO_XXHASH + fds->xxhPos = 0; +#endif + fds->loopCount = 0; + fds->wait = 0; +} + +FL2LIB_API FL2_DStream *FL2LIB_CALL FL2_createDStreamMt(unsigned nbThreads) +{ + FL2_DStream* const fds = malloc(sizeof(FL2_DStream)); + DEBUGLOG(3, "FL2_createDStream"); + + if (fds != NULL) { + LZMA_constructDCtx(&fds->dec); + + nbThreads = FL2_checkNbThreads(nbThreads); + + FL2_resetDStream(fds); + fds->timeout = 0; + +#ifndef FL2_SINGLETHREAD + fds->decompressThread = NULL; + fds->decmt = (nbThreads > 1) ? FL2_lzma2DecMt_create(nbThreads) : NULL; +#endif + +#ifndef NO_XXHASH + fds->xxh = NULL; +#endif + fds->doHash = 0; + } + + return fds; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_freeDStream(FL2_DStream* fds) +{ + if (fds != NULL) { + DEBUGLOG(3, "FL2_freeDStream"); + LZMA_destructDCtx(&fds->dec); +#ifndef FL2_SINGLETHREAD + FL2POOL_free(fds->decompressThread); + FL2_lzma2DecMt_free(fds->decmt); +#endif +#ifndef NO_XXHASH + XXH32_freeState(fds->xxh); +#endif + free(fds); + } + return 0; +} + +FL2LIB_API void FL2LIB_CALL FL2_setDStreamMemoryLimitMt(FL2_DStream * fds, size_t limit) +{ +#ifndef FL2_SINGLETHREAD + if (fds->decmt != NULL) + fds->decmt->memLimit = limit; +#endif +} + +FL2LIB_API size_t FL2LIB_CALL FL2_initDStream(FL2_DStream* fds) +{ + DEBUGLOG(4, "FL2_initDStream"); + + if (fds->wait) + return FL2_ERROR(stage_wrong); + + FL2_resetDStream(fds); + +#ifndef FL2_SINGLETHREAD + FL2_lzma2DecMt_init(fds->decmt); +#endif + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_setDStreamTimeout(FL2_DStream * fds, unsigned timeout) +{ +#ifndef FL2_SINGLETHREAD + /* decompressThread is only used if a timeout is specified */ + if (timeout != 0) { + if (fds->decompressThread == NULL) { + fds->decompressThread = FL2POOL_create(1); + if (fds->decompressThread == NULL) + return FL2_ERROR(memory_allocation); + } + } + else if (!fds->wait) { + /* Only free the thread if decompression not underway */ + FL2POOL_free(fds->decompressThread); + fds->decompressThread = NULL; + } + fds->timeout = timeout; +#endif + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_waitDStream(FL2_DStream * fds) +{ +#ifndef FL2_SINGLETHREAD + if (FL2POOL_waitAll(fds->decompressThread, fds->timeout) != 0) + return FL2_ERROR(timedOut); +#endif + /* decompressThread writes the result into asyncRes before sleeping */ + return fds->asyncRes; +} + +FL2LIB_API void FL2LIB_CALL FL2_cancelDStream(FL2_DStream *fds) +{ +#ifndef FL2_SINGLETHREAD + if (fds->decompressThread != NULL) { + fds->decmt->canceled = 1; + + FL2POOL_waitAll(fds->decompressThread, 0); + + fds->decmt->canceled = 0; + } + FL2_lzma2DecMt_cleanup(fds->decmt); +#endif +} + +FL2LIB_API unsigned long long FL2LIB_CALL FL2_getDStreamProgress(const FL2_DStream * fds) +{ + return fds->streamTotal + fds->progress; +} + +static size_t FL2_initDStream_prop(FL2_DStream* const fds, BYTE prop) +{ + fds->doHash = prop >> FL2_PROP_HASH_BIT; + prop &= FL2_LZMA_PROP_MASK; + + /* If MT decoding is enabled and the dict is not too large, decoder init will occur elsewhere */ +#ifndef FL2_SINGLETHREAD + if (fds->decmt == NULL || FL2_lzma2DecMt_initProp(fds->decmt, prop)) +#endif + CHECK_F(LZMA2_initDecoder(&fds->dec, prop, NULL, 0)); + +#ifndef NO_XXHASH + if (fds->doHash) { + if (fds->xxh == NULL) { + DEBUGLOG(3, "Creating hash state"); + fds->xxh = XXH32_createState(); + if (fds->xxh == NULL) + return FL2_ERROR(memory_allocation); + } + XXH32_reset(fds->xxh, 0); + } +#endif + return FL2_error_no_error; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_initDStream_withProp(FL2_DStream* fds, unsigned char prop) +{ + CHECK_F(FL2_initDStream(fds)); + CHECK_F(FL2_initDStream_prop(fds, prop)); + fds->stage = FL2DEC_STAGE_DECOMP; + return FL2_error_no_error; +} + +static size_t FL2_decompressStream_blocking(FL2_DStream* fds, FL2_outBuffer* output, FL2_inBuffer* input) +{ +#ifndef FL2_SINGLETHREAD + FL2_decMt *const decmt = fds->decmt; +#endif + size_t const prevOut = output->pos; + size_t const prevIn = input->pos; + + if (input->pos < input->size +#ifndef FL2_SINGLETHREAD + || decmt +#endif + ) { + if (fds->stage == FL2DEC_STAGE_INIT) { + BYTE prop = ((const BYTE*)input->src)[input->pos]; + ++input->pos; + FL2_initDStream_prop(fds, prop); + fds->stage = FL2DEC_STAGE_DECOMP; + } +#ifndef FL2_SINGLETHREAD + if (decmt) { + size_t res = FL2_decompressStreamMt(fds, output, input); + if (FL2_isError(res)) { + FL2_lzma2DecMt_cleanup(decmt); + return res; + } + } + else +#endif + { + CHECK_F(FL2_decompressOverlappedInput(fds, output, input)); + } + if (fds->stage == FL2DEC_STAGE_HASH) { +#ifndef NO_XXHASH +#ifndef FL2_SINGLETHREAD + if (fds->overlapSize != 0) { + /* Must copy buffered data before using input */ + size_t toRead = MIN(XXHASH_SIZEOF - fds->xxhPos, fds->overlapSize); + memcpy(fds->xxhIn.digest + fds->xxhPos, fds->overlap, toRead); + fds->xxhPos += toRead; + fds->overlapSize = 0; + } +#endif + size_t toRead = MIN(XXHASH_SIZEOF - fds->xxhPos, input->size - input->pos); + memcpy(fds->xxhIn.digest + fds->xxhPos, (BYTE*)input->src + input->pos, toRead); + input->pos += toRead; + fds->xxhPos += toRead; + if (fds->xxhPos == XXHASH_SIZEOF) { + DEBUGLOG(4, "Checking hash"); + U32 hash = XXH32_hashFromCanonical(&fds->xxhIn); + if (hash != XXH32_digest(fds->xxh)) + return FL2_ERROR(checksum_wrong); + fds->stage = FL2DEC_STAGE_FINISHED; + } +#else + fds->stage = FL2DEC_STAGE_FINISHED; +#endif /* NO_XXHASH */ + } + } + if (fds->stage != FL2DEC_STAGE_FINISHED && prevOut == output->pos && prevIn == input->pos) { + /* No progress was made */ + ++fds->loopCount; + if (fds->loopCount > 2) { + FL2_cancelDStream(fds); + return FL2_ERROR(buffer); + } + } + else { + fds->loopCount = 0; + } + + if (fds->stage == FL2DEC_STAGE_FINISHED) { +#ifndef FL2_SINGLETHREAD + FL2_lzma2DecMt_cleanup(decmt); +#endif + return 0; + } + else { + return 1; + } +} + +/* FL2_decompressStream_async() : FL2POOL_function type */ +static void FL2_decompressStream_async(void* const jobDescription, ptrdiff_t const n) +{ + FL2_DStream* const fds = (FL2_DStream*)jobDescription; + + fds->asyncRes = FL2_decompressStream_blocking(fds, fds->asyncOutput, fds->asyncInput); + fds->wait = 0; + + (void)n; +} + +FL2LIB_API size_t FL2LIB_CALL FL2_decompressStream(FL2_DStream* fds, FL2_outBuffer* output, FL2_inBuffer* input) +{ + fds->streamTotal += fds->progress; + fds->progress = 0; + +#ifndef FL2_SINGLETHREAD + if (fds->decompressThread != NULL) { + /* Calling FL2_decompressStream() while waiting for decompressThread to fall idle is not allowed */ + if (fds->wait) + return FL2_ERROR(stage_wrong); + + fds->asyncOutput = output; + fds->asyncInput = input; + /* FL2_decompressStream_async will reset fds->wait upon completion */ + fds->wait = 1; + + FL2POOL_add(fds->decompressThread, FL2_decompressStream_async, fds, 0); + + /* Wait for completion or a timeout */ + CHECK_F(FL2_waitDStream(fds)); + + /* FL2_decompressStream_async() stores result in asyncRes */ + return fds->asyncRes; + } + else +#endif + { + return FL2_decompressStream_blocking(fds, output, input); + } +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateDCtxSize(unsigned nbThreads) +{ + nbThreads = FL2_checkNbThreads(nbThreads); + if (nbThreads > 1) + return nbThreads * (sizeof(FL2_blockDecMt) + sizeof(FL2_DCtx)); + + return sizeof(FL2_DCtx); +} + +FL2LIB_API size_t FL2LIB_CALL FL2_estimateDStreamSize(size_t dictSize, unsigned nbThreads) +{ + nbThreads = FL2_checkNbThreads(nbThreads); + if (nbThreads > 1) { + /* Estimate 50% compression and a block size of 4 * dictSize */ + return nbThreads * sizeof(FL2_DCtx) + (dictSize + dictSize / 2) * 4 * nbThreads; + } + return LZMA2_decMemoryUsage(dictSize); +} \ No newline at end of file diff --git a/builtins/flzma2/fl2_errors.h b/builtins/flzma2/fl2_errors.h new file mode 100644 index 0000000000000..1068f463fa189 --- /dev/null +++ b/builtins/flzma2/fl2_errors.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * Modified for FL2 by Conor McCarthy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef FL2_ERRORS_H_398273423 +#define FL2_ERRORS_H_398273423 + +#if defined (__cplusplus) +extern "C" { +#endif + +/*===== dependency =====*/ +#include /* size_t */ + +#include "fast-lzma2.h" + +/*-**************************************** + * error codes list + * note : this API is still considered unstable + * and shall not be used with a dynamic library. + * only static linking is allowed + ******************************************/ +typedef enum { + FL2_error_no_error = 0, + FL2_error_GENERIC = 1, + FL2_error_internal = 2, + FL2_error_corruption_detected = 3, + FL2_error_checksum_wrong = 4, + FL2_error_parameter_unsupported = 5, + FL2_error_parameter_outOfBound = 6, + FL2_error_lclpMax_exceeded = 7, + FL2_error_stage_wrong = 8, + FL2_error_init_missing = 9, + FL2_error_memory_allocation = 10, + FL2_error_dstSize_tooSmall = 11, + FL2_error_srcSize_wrong = 12, + FL2_error_canceled = 13, + FL2_error_buffer = 14, + FL2_error_timedOut = 15, + FL2_error_maxCode = 20 /* never EVER use this value directly, it can change in future versions! Use FL2_isError() instead */ +} FL2_ErrorCode; + +/*! FL2_getErrorCode() : + convert a `size_t` function result into a `FL2_ErrorCode` enum type, + which can be used to compare with enum list published above */ +FL2LIB_API FL2_ErrorCode FL2LIB_CALL FL2_getErrorCode(size_t functionResult); +FL2LIB_API const char* FL2LIB_CALL FL2_getErrorString(FL2_ErrorCode code); /**< Same as FL2_getErrorName, but using a `FL2_ErrorCode` enum argument */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* FL2_ERRORS_H_398273423 */ diff --git a/builtins/flzma2/fl2_internal.h b/builtins/flzma2/fl2_internal.h new file mode 100644 index 0000000000000..9f6664580dcea --- /dev/null +++ b/builtins/flzma2/fl2_internal.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * Modified for FL2 by Conor McCarthy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef FL2_INTERNAL_H_ +#define FL2_INTERNAL_H_ + + +/*-************************************* +* Dependencies +***************************************/ +#include "mem.h" +#include "compiler.h" + + +#if defined (__cplusplus) +extern "C" { +#endif + + +/*-**************************************** +* Error codes handling +******************************************/ +#define PREFIX(name) FL2_error_##name +#define FL2_ERROR(name) ((size_t)-PREFIX(name)) + + +/*-************************************* +* Stream properties +***************************************/ +#define FL2_PROP_HASH_BIT 7 +#define FL2_LZMA_PROP_MASK 0x3FU +#ifndef NO_XXHASH +# define XXHASH_SIZEOF sizeof(XXH32_canonical_t) +#endif + + +/*-************************************* +* Debug +***************************************/ +#if defined(FL2_DEBUG) && (FL2_DEBUG>=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define FL2_STATIC_ASSERT(c) { enum { FL2_static_assert = 1/(int)(!!(c)) }; } + +#if defined(FL2_DEBUG) && (FL2_DEBUG>=2) +# include +extern int g_debuglog_enable; +/* recommended values for FL2_DEBUG display levels : + * 1 : no display, enables assert() only + * 2 : reserved for currently active debugging path + * 3 : events once per object lifetime (CCtx, CDict) + * 4 : events once per frame + * 5 : events once per block + * 6 : events once per sequence (*very* verbose) */ +# define RAWLOG(l, ...) { \ + if ((g_debuglog_enable) & (l<=FL2_DEBUG)) { \ + fprintf(stderr, __VA_ARGS__); \ + } } +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) & (l<=FL2_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define RAWLOG(l, ...) {} /* disabled */ +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************* +* shared macros +***************************************/ +#undef MIN +#undef MAX +#define MIN(a,b) ((a)<(b) ? (a) : (b)) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) +#define CHECK_F(f) do { size_t const errcod = f; if (FL2_isError(errcod)) return errcod; } while(0) /* check and Forward error code */ +#define CHECK_E(f, e) do { size_t const errcod = f; if (FL2_isError(errcod)) return FL2_ERROR(e); } while(0) /* check and send Error code */ + +MEM_STATIC U32 ZSTD_highbit32(U32 val) +{ + assert(val != 0); + { +# if defined(_MSC_VER) /* Visual */ + unsigned long r=0; + _BitScanReverse(&r, val); + return (unsigned)r; +# elif defined(__GNUC__) && (__GNUC__ >= 3) /* GCC Intrinsic */ + return 31 - __builtin_clz(val); +# else /* Software version */ + static const int DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; + U32 v = val; + int r; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + r = DeBruijnClz[(U32)(v * 0x07C4ACDDU) >> 27]; + return r; +# endif + } +} + + +#if defined (__cplusplus) +} +#endif + +#endif /* FL2_INTERNAL_H_ */ diff --git a/builtins/flzma2/fl2_pool.c b/builtins/flzma2/fl2_pool.c new file mode 100644 index 0000000000000..8f90b44cb8f74 --- /dev/null +++ b/builtins/flzma2/fl2_pool.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * Modified for FL2 by Conor McCarthy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + + +/* ====== Dependencies ======= */ +#include /* size_t */ +#include /* malloc, calloc */ +#include "fl2_pool.h" +#include "fl2_internal.h" + + +#ifndef FL2_SINGLETHREAD + +#include "fl2_threading.h" /* pthread adaptation */ + +struct FL2POOL_ctx_s { + /* Keep track of the threads */ + size_t numThreads; + + /* All threads work on the same function and object during a job */ + FL2POOL_function function; + void *opaque; + + /* The number of threads working on jobs */ + size_t numThreadsBusy; + /* Indicates the number of threads requested and the values to pass */ + ptrdiff_t queueIndex; + ptrdiff_t queueEnd; + + /* The mutex protects the queue */ + FL2_pthread_mutex_t queueMutex; + /* Condition variable for pushers to wait on when the queue is full */ + FL2_pthread_cond_t busyCond; + /* Condition variable for poppers to wait on when the queue is empty */ + FL2_pthread_cond_t newJobsCond; + /* Indicates if the queue is shutting down */ + int shutdown; + + /* The threads. Extras to be calloc'd */ + FL2_pthread_t threads[1]; +}; + +/* FL2POOL_thread() : + Work thread for the thread pool. + Waits for jobs and executes them. + @returns : NULL on failure else non-null. +*/ +static void* FL2POOL_thread(void* opaque) +{ + FL2POOL_ctx* const ctx = (FL2POOL_ctx*)opaque; + if (!ctx) { return NULL; } + FL2_pthread_mutex_lock(&ctx->queueMutex); + for (;;) { + + /* While the mutex is locked, wait for a non-empty queue or until shutdown */ + while (ctx->queueIndex >= ctx->queueEnd && !ctx->shutdown) { + FL2_pthread_cond_wait(&ctx->newJobsCond, &ctx->queueMutex); + } + /* empty => shutting down: so stop */ + if (ctx->shutdown) { + FL2_pthread_mutex_unlock(&ctx->queueMutex); + return opaque; + } + /* Pop a job off the queue */ + size_t n = ctx->queueIndex; + ++ctx->queueIndex; + ++ctx->numThreadsBusy; + /* Unlock the mutex and run the job */ + FL2_pthread_mutex_unlock(&ctx->queueMutex); + + ctx->function(ctx->opaque, n); + + FL2_pthread_mutex_lock(&ctx->queueMutex); + --ctx->numThreadsBusy; + /* Signal the master thread waiting for jobs to complete */ + FL2_pthread_cond_signal(&ctx->busyCond); + } /* for (;;) */ + /* Unreachable */ +} + +FL2POOL_ctx* FL2POOL_create(size_t numThreads) +{ + FL2POOL_ctx* ctx; + /* Check the parameters */ + if (!numThreads) { return NULL; } + /* Allocate the context and zero initialize */ + ctx = calloc(1, sizeof(FL2POOL_ctx) + (numThreads - 1) * sizeof(FL2_pthread_t)); + if (!ctx) { return NULL; } + /* Initialize the busy count and jobs range */ + ctx->numThreadsBusy = 0; + ctx->queueIndex = 0; + ctx->queueEnd = 0; + (void)FL2_pthread_mutex_init(&ctx->queueMutex, NULL); + (void)FL2_pthread_cond_init(&ctx->busyCond, NULL); + (void)FL2_pthread_cond_init(&ctx->newJobsCond, NULL); + ctx->shutdown = 0; + ctx->numThreads = 0; + /* Initialize the threads */ + { size_t i; + for (i = 0; i < numThreads; ++i) { + if (FL2_pthread_create(&ctx->threads[i], NULL, &FL2POOL_thread, ctx)) { + ctx->numThreads = i; + FL2POOL_free(ctx); + return NULL; + } } + ctx->numThreads = numThreads; + } + return ctx; +} + +/*! FL2POOL_join() : + Shutdown the queue, wake any sleeping threads, and join all of the threads. +*/ +static void FL2POOL_join(FL2POOL_ctx* ctx) +{ + /* Shut down the queue */ + FL2_pthread_mutex_lock(&ctx->queueMutex); + ctx->shutdown = 1; + /* Wake up sleeping threads */ + FL2_pthread_cond_broadcast(&ctx->newJobsCond); + FL2_pthread_mutex_unlock(&ctx->queueMutex); + /* Join all of the threads */ + for (size_t i = 0; i < ctx->numThreads; ++i) + FL2_pthread_join(ctx->threads[i], NULL); +} + +void FL2POOL_free(FL2POOL_ctx *ctx) +{ + if (!ctx) { return; } + FL2POOL_join(ctx); + FL2_pthread_mutex_destroy(&ctx->queueMutex); + FL2_pthread_cond_destroy(&ctx->busyCond); + FL2_pthread_cond_destroy(&ctx->newJobsCond); + free(ctx); +} + +size_t FL2POOL_sizeof(FL2POOL_ctx *ctx) +{ + if (ctx==NULL) return 0; /* supports sizeof NULL */ + return sizeof(*ctx) + ctx->numThreads * sizeof(FL2_pthread_t); +} + +void FL2POOL_addRange(void* ctxVoid, FL2POOL_function function, void *opaque, ptrdiff_t first, ptrdiff_t end) +{ + FL2POOL_ctx* const ctx = (FL2POOL_ctx*)ctxVoid; + if (!ctx) + return; + + /* Callers always wait for jobs to complete before adding a new set */ + assert(!ctx->numThreadsBusy); + + FL2_pthread_mutex_lock(&ctx->queueMutex); + ctx->function = function; + ctx->opaque = opaque; + ctx->queueIndex = first; + ctx->queueEnd = end; + FL2_pthread_cond_broadcast(&ctx->newJobsCond); + FL2_pthread_mutex_unlock(&ctx->queueMutex); +} + +void FL2POOL_add(void* ctxVoid, FL2POOL_function function, void *opaque, ptrdiff_t n) +{ + FL2POOL_addRange(ctxVoid, function, opaque, n, n + 1); +} + +int FL2POOL_waitAll(void *ctxVoid, unsigned timeout) +{ + FL2POOL_ctx* const ctx = (FL2POOL_ctx*)ctxVoid; + if (!ctx || (!ctx->numThreadsBusy && ctx->queueIndex >= ctx->queueEnd) || ctx->shutdown) { return 0; } + + FL2_pthread_mutex_lock(&ctx->queueMutex); + /* Need to test for ctx->queueIndex < ctx->queueEnd in case not all jobs have started */ + if (timeout != 0) { + if ((ctx->numThreadsBusy || ctx->queueIndex < ctx->queueEnd) && !ctx->shutdown) + FL2_pthread_cond_timedwait(&ctx->busyCond, &ctx->queueMutex, timeout); + } + else { + while ((ctx->numThreadsBusy || ctx->queueIndex < ctx->queueEnd) && !ctx->shutdown) + FL2_pthread_cond_wait(&ctx->busyCond, &ctx->queueMutex); + } + FL2_pthread_mutex_unlock(&ctx->queueMutex); + return ctx->numThreadsBusy && !ctx->shutdown; +} + +size_t FL2POOL_threadsBusy(void * ctx) +{ + return ((FL2POOL_ctx*)ctx)->numThreadsBusy; +} + +#endif /* FL2_SINGLETHREAD */ diff --git a/builtins/flzma2/fl2_pool.h b/builtins/flzma2/fl2_pool.h new file mode 100644 index 0000000000000..ccf1d0031bdba --- /dev/null +++ b/builtins/flzma2/fl2_pool.h @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2016-present, Yann Collet, Facebook, Inc. +* All rights reserved. +* Modified for FL2 by Conor McCarthy +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#ifndef FL2POOL_H +#define FL2POOL_H + +#if defined (__cplusplus) +extern "C" { +#endif + + +#include /* size_t */ + +typedef struct FL2POOL_ctx_s FL2POOL_ctx; + +/*! FL2POOL_create() : +* Create a thread pool with at most `numThreads` threads. +* `numThreads` must be at least 1. +* @return : FL2POOL_ctx pointer on success, else NULL. +*/ +FL2POOL_ctx *FL2POOL_create(size_t numThreads); + + +/*! FL2POOL_free() : +Free a thread pool returned by FL2POOL_create(). +*/ +void FL2POOL_free(FL2POOL_ctx *ctx); + +/*! FL2POOL_sizeof() : +return memory usage of pool returned by FL2POOL_create(). +*/ +size_t FL2POOL_sizeof(FL2POOL_ctx *ctx); + +/*! FL2POOL_function : +The function type that can be added to a thread pool. +*/ +typedef void(*FL2POOL_function)(void *, ptrdiff_t); + +/*! FL2POOL_add() : +Add the job `function(opaque)` to the thread pool. +FL2POOL_addRange adds multiple jobs with size_t parameter from first to less than end. +Possibly blocks until there is room in the queue. +Note : The function may be executed asynchronously, so `opaque` must live until the function has been completed. +*/ +void FL2POOL_add(void* ctxVoid, FL2POOL_function function, void *opaque, ptrdiff_t n); +void FL2POOL_addRange(void *ctx, FL2POOL_function function, void *opaque, ptrdiff_t first, ptrdiff_t end); + +int FL2POOL_waitAll(void *ctx, unsigned timeout); + +size_t FL2POOL_threadsBusy(void *ctx); + +#if defined (__cplusplus) +} +#endif + +#endif diff --git a/builtins/flzma2/fl2_threading.c b/builtins/flzma2/fl2_threading.c new file mode 100644 index 0000000000000..d4ac2e91b42b9 --- /dev/null +++ b/builtins/flzma2/fl2_threading.c @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2016 Tino Reichardt + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * + * You can contact the author at: + * - zstdmt source repository: https://github.com/mcmilk/zstdmt + */ + +/** + * This file will hold wrapper for systems, which do not support pthreads + */ + +/* create fake symbol to avoid empty translation unit warning */ +int g_ZSTD_threading_useles_symbol; + +#include "fast-lzma2.h" +#include "fl2_threading.h" +#include "util.h" + +#if !defined(FL2_SINGLETHREAD) && defined(_WIN32) + +/** + * Windows minimalist Pthread Wrapper, based on : + * http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + */ + + +/* === Dependencies === */ +#include +#include + + +/* === Implementation === */ + +static unsigned __stdcall worker(void *arg) +{ + FL2_pthread_t* const thread = (FL2_pthread_t*) arg; + thread->arg = thread->start_routine(thread->arg); + return 0; +} + +int FL2_pthread_create(FL2_pthread_t* thread, const void* unused, + void* (*start_routine) (void*), void* arg) +{ + (void)unused; + thread->arg = arg; + thread->start_routine = start_routine; + thread->handle = (HANDLE) _beginthreadex(NULL, 0, worker, thread, 0, NULL); + + if (!thread->handle) + return errno; + else + return 0; +} + +int FL2_pthread_join(FL2_pthread_t thread, void **value_ptr) +{ + DWORD result; + + if (!thread.handle) return 0; + + result = WaitForSingleObject(thread.handle, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + if (value_ptr) *value_ptr = thread.arg; + return 0; + case WAIT_ABANDONED: + return EINVAL; + default: + return GetLastError(); + } +} + +#endif /* FL2_SINGLETHREAD */ + +unsigned FL2_checkNbThreads(unsigned nbThreads) +{ +#ifndef FL2_SINGLETHREAD + if (nbThreads == 0) { + nbThreads = UTIL_countPhysicalCores(); + nbThreads += !nbThreads; + } + if (nbThreads > FL2_MAXTHREADS) { + nbThreads = FL2_MAXTHREADS; + } +#else + nbThreads = 1; +#endif + return nbThreads; +} + diff --git a/builtins/flzma2/fl2_threading.h b/builtins/flzma2/fl2_threading.h new file mode 100644 index 0000000000000..c9259b0c42a42 --- /dev/null +++ b/builtins/flzma2/fl2_threading.h @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2016 Tino Reichardt + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * + * You can contact the author at: + * - zstdmt source repository: https://github.com/mcmilk/zstdmt + */ + +#ifndef THREADING_H_938743 +#define THREADING_H_938743 + +#include "mem.h" + +#ifndef FL2_XZ_BUILD +# ifdef _WIN32 +# define MYTHREAD_VISTA +# else +# define MYTHREAD_POSIX /* posix assumed ; need a better detection method */ +# endif +#elif defined(HAVE_CONFIG_H) +# include +#endif + +#if defined (__cplusplus) +extern "C" { +#endif + +unsigned FL2_checkNbThreads(unsigned nbThreads); + + +#if !defined(FL2_SINGLETHREAD) && defined(MYTHREAD_VISTA) + +/** + * Windows minimalist Pthread Wrapper, based on : + * http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + */ +#ifdef WINVER +# undef WINVER +#endif +#define WINVER 0x0600 + +#ifdef _WIN32_WINNT +# undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include +#include + + +/* mutex */ +#define FL2_pthread_mutex_t CRITICAL_SECTION +#define FL2_pthread_mutex_init(a, b) (InitializeCriticalSection((a)), 0) +#define FL2_pthread_mutex_destroy(a) DeleteCriticalSection((a)) +#define FL2_pthread_mutex_lock(a) EnterCriticalSection((a)) +#define FL2_pthread_mutex_unlock(a) LeaveCriticalSection((a)) + +/* condition variable */ +#define FL2_pthread_cond_t CONDITION_VARIABLE +#define FL2_pthread_cond_init(a, b) (InitializeConditionVariable((a)), 0) +#define FL2_pthread_cond_destroy(a) /* No delete */ +#define FL2_pthread_cond_wait(a, b) SleepConditionVariableCS((a), (b), INFINITE) +#define FL2_pthread_cond_timedwait(a, b, c) SleepConditionVariableCS((a), (b), (c)) +#define FL2_pthread_cond_signal(a) WakeConditionVariable((a)) +#define FL2_pthread_cond_broadcast(a) WakeAllConditionVariable((a)) + +/* FL2_pthread_create() and FL2_pthread_join() */ +typedef struct { + HANDLE handle; + void* (*start_routine)(void*); + void* arg; +} FL2_pthread_t; + +int FL2_pthread_create(FL2_pthread_t* thread, const void* unused, + void* (*start_routine) (void*), void* arg); + +int FL2_pthread_join(FL2_pthread_t thread, void** value_ptr); + +/** + * add here more wrappers as required + */ + + +#elif !defined(FL2_SINGLETHREAD) && defined(MYTHREAD_POSIX) +/* === POSIX Systems === */ +# include +# include + +#define FL2_pthread_mutex_t pthread_mutex_t +#define FL2_pthread_mutex_init(a, b) pthread_mutex_init((a), (b)) +#define FL2_pthread_mutex_destroy(a) pthread_mutex_destroy((a)) +#define FL2_pthread_mutex_lock(a) pthread_mutex_lock((a)) +#define FL2_pthread_mutex_unlock(a) pthread_mutex_unlock((a)) + +#define FL2_pthread_cond_t pthread_cond_t +#define FL2_pthread_cond_init(a, b) pthread_cond_init((a), (b)) +#define FL2_pthread_cond_destroy(a) pthread_cond_destroy((a)) +#define FL2_pthread_cond_wait(a, b) pthread_cond_wait((a), (b)) +#define FL2_pthread_cond_signal(a) pthread_cond_signal((a)) +#define FL2_pthread_cond_broadcast(a) pthread_cond_broadcast((a)) + +#define FL2_pthread_t pthread_t +#define FL2_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d)) +#define FL2_pthread_join(a, b) pthread_join((a),(b)) + +/* Timed wait functions from XZ by Lasse Collin +*/ + +/* Sets condtime to the absolute time that is timeout_ms milliseconds + * in the future. + */ +static inline void +mythread_condtime_set(struct timespec *condtime, U32 timeout_ms) +{ + condtime->tv_sec = timeout_ms / 1000; + condtime->tv_nsec = (timeout_ms % 1000) * 1000000; + + struct timeval now; + gettimeofday(&now, NULL); + + condtime->tv_sec += now.tv_sec; + condtime->tv_nsec += now.tv_usec * 1000L; + + /* tv_nsec must stay in the range [0, 999_999_999]. */ + if (condtime->tv_nsec >= 1000000000L) { + condtime->tv_nsec -= 1000000000L; + ++condtime->tv_sec; + } +} + +/* Waits on a condition or until a timeout expires. If the timeout expires, + * non-zero is returned, otherwise zero is returned. + */ +static inline void +FL2_pthread_cond_timedwait(FL2_pthread_cond_t *cond, FL2_pthread_mutex_t *mutex, + U32 timeout_ms) +{ + struct timespec condtime; + mythread_condtime_set(&condtime, timeout_ms); + pthread_cond_timedwait(cond, mutex, &condtime); +} + + +#elif defined(FL2_SINGLETHREAD) +/* No multithreading support */ + +typedef int FL2_pthread_mutex_t; +#define FL2_pthread_mutex_init(a, b) ((void)a, 0) +#define FL2_pthread_mutex_destroy(a) +#define FL2_pthread_mutex_lock(a) +#define FL2_pthread_mutex_unlock(a) + +typedef int FL2_pthread_cond_t; +#define FL2_pthread_cond_init(a, b) ((void)a, 0) +#define FL2_pthread_cond_destroy(a) +#define FL2_pthread_cond_wait(a, b) +#define FL2_pthread_cond_signal(a) +#define FL2_pthread_cond_broadcast(a) + +/* do not use FL2_pthread_t */ + +#else +# error FL2_SINGLETHREAD not defined but no threading support found +#endif /* FL2_SINGLETHREAD */ + +#if defined (__cplusplus) +} +#endif + +#endif /* THREADING_H_938743 */ diff --git a/builtins/flzma2/lzma2_dec.c b/builtins/flzma2/lzma2_dec.c new file mode 100644 index 0000000000000..80bfcd76f9d27 --- /dev/null +++ b/builtins/flzma2/lzma2_dec.c @@ -0,0 +1,1381 @@ +/* lzma2_dec.c -- LZMA2 Decoder +Based upon LzmaDec.c 2018-02-28 : Igor Pavlov : Public domain +Modified for FL2 by Conor McCarthy */ + +#include +#include "fl2_errors.h" +#include "fl2_internal.h" +#include "lzma2_dec.h" +#include "platform.h" + + +#ifdef HAVE_SMALL +# define LZMA_SIZE_OPT +#endif + +#define kNumTopBits 24 +#define kTopValue ((U32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (LZMA2_prob)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (LZMA2_prob)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } + +#if defined __x86_64__s || defined _M_X64 + +#define USE_CMOV + +#define PREP_BIT(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt +#define UPDATE_PREP_0 U32 r0 = bound; unsigned p0 = (ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)) +#define UPDATE_PREP_1 U32 r1 = range - bound; unsigned p1 = (ttt - (ttt >> kNumMoveBits)) +#define UPDATE_COND(p) range=(code < bound) ? r0 : r1; *p = (LZMA2_prob)((code < bound) ? p0 : p1) +#define UPDATE_CODE code = code - ((code < bound) ? 0 : bound) + +#define TREE_GET_BIT(probs, i) { LZMA2_prob *pp = (probs)+(i); PREP_BIT(pp); \ + UPDATE_PREP_0; unsigned i0 = (i + i); \ + UPDATE_PREP_1; unsigned i1 = (i + i) + 1; \ + UPDATE_COND(pp); \ + i = (code < bound) ? i0 : i1; \ + UPDATE_CODE; \ +} + +#define REV_BIT_VAR(probs, i, m) { LZMA2_prob *pp = (probs)+(i); PREP_BIT(pp); \ + UPDATE_PREP_0; U32 i0 = i + m; U32 m2 = m + m; \ + UPDATE_PREP_1; U32 i1 = i + m2; \ + UPDATE_COND(pp); \ + i = (code < bound) ? i0 : i1; \ + m = m2; \ + UPDATE_CODE; \ +} +#define REV_BIT_CONST(probs, i, m) { LZMA2_prob *pp = (probs)+(i); PREP_BIT(pp); \ + UPDATE_PREP_0; \ + UPDATE_PREP_1; \ + UPDATE_COND(pp); \ + i += m + (code < bound ? 0 : m); \ + UPDATE_CODE; \ +} +#define REV_BIT_LAST(probs, i, m) { LZMA2_prob *pp = (probs)+(i); PREP_BIT(pp); \ + UPDATE_PREP_0; \ + UPDATE_PREP_1; \ + UPDATE_COND(pp); \ + i -= code < bound ? m : 0; \ + UPDATE_CODE; \ +} + +#define MATCHED_LITER_DEC \ + match_byte += match_byte; \ + bit = offs; \ + offs &= match_byte; \ + prob_lit = prob + (offs + bit + symbol); \ + PREP_BIT(prob_lit); \ + { UPDATE_PREP_0; unsigned i0 = (symbol + symbol); \ + UPDATE_PREP_1; unsigned i1 = (symbol + symbol) + 1; \ + UPDATE_COND(prob_lit); \ + symbol = (code < bound) ? i0 : i1; \ + offs = (code < bound) ? offs ^ bit : offs; \ + UPDATE_CODE; } + +#else +#define TREE_GET_BIT(probs, i) { GET_BIT2(probs + i, i, ;, ;); } + +#define REV_BIT(p, i, A0, A1) IF_BIT_0(p + i) \ + { UPDATE_0(p + i); A0; } else \ + { UPDATE_1(p + i); A1; } +#define REV_BIT_VAR( p, i, m) REV_BIT(p, i, i += m; m += m, m += m; i += m; ) +#define REV_BIT_CONST(p, i, m) REV_BIT(p, i, i += m; , i += m * 2; ) +#define REV_BIT_LAST( p, i, m) REV_BIT(p, i, i -= m , ; ) + +#define MATCHED_LITER_DEC \ + match_byte += match_byte; \ + bit = offs; \ + offs &= match_byte; \ + prob_lit = prob + (offs + bit + symbol); \ + GET_BIT2(prob_lit, symbol, offs ^= bit; , ;) + +#endif + +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +#ifdef LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMAL_LITER_DEC TREE_GET_BIT(prob, symbol) + +#define NORMALIZE_CHECK if (range < kTopValue) { return 0; } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + +#define REV_BIT_CHECK(p, i, m) IF_BIT_0_CHECK(p + i) \ + { UPDATE_0_CHECK; i += m; m += m; } else \ + { UPDATE_1_CHECK; m += m; i += m; } + +/* +00000000 - EOS +00000001 U U - Uncompressed Reset Dic +00000010 U U - Uncompressed No Reset +100uuuuu U U P P - LZMA no reset +101uuuuu U U P P - LZMA reset state +110uuuuu U U P P S - LZMA reset state + new prop +111uuuuu U U P P S - LZMA reset state + new prop + reset dic + +u, U - Unpack Size +P - Pack Size +S - Props +*/ + +#define LZMA2_CONTROL_LZMA (1 << 7) +#define LZMA2_CONTROL_COPY_NO_RESET 2 +#define LZMA2_CONTROL_COPY_RESET_DIC 1 +#define LZMA2_CONTROL_EOF 0 + +#define LZMA2_IS_UNCOMPRESSED_STATE(control) ((control & LZMA2_CONTROL_LZMA) == 0) + +#define LZMA2_GET_LZMA_MODE(control) ((control >> 5) & 3) +#define LZMA2_IS_THERE_PROP(mode) ((mode) >= 2) + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +typedef enum +{ + LZMA2_STATE_CONTROL, + LZMA2_STATE_DATA, + LZMA2_STATE_FINISHED, + LZMA2_STATE_ERROR +} LZMA2_state; + +#define LZMA_DIC_MIN (1 << 12) + +static BYTE LZMA_tryDummy(const LZMA2_DCtx *const p) +{ + const LZMA2_prob *probs = GET_PROBS; + unsigned state = p->state; + U32 range = p->range; + U32 code = p->code; + + const LZMA2_prob *prob; + U32 bound; + unsigned ttt; + unsigned pos_state = CALC_POS_STATE(p->processed_pos, (1 << p->prop.pb) - 1); + + prob = probs + IsMatch + COMBINED_PS_STATE; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + prob = probs + Literal; + if (p->check_dic_size != 0 || p->processed_pos != 0) + prob += ((U32)kLzmaLitSize * + ((((p->processed_pos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dic_pos == 0 ? p->dic_buf_size : p->dic_pos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned match_byte = p->dic[p->dic_pos - p->reps[0] + + (p->dic_pos < p->reps[0] ? p->dic_buf_size : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + const LZMA2_prob *prob_lit; + match_byte += match_byte; + bit = offs; + offs &= match_byte; + prob_lit = prob + (offs + bit + symbol); + GET_BIT2_CHECK(prob_lit, symbol, offs ^= bit; , ; ) + } while (symbol < 0x100); + } + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + COMBINED_PS_STATE; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return 1; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + const LZMA2_prob *prob_len = prob + LenChoice; + IF_BIT_0_CHECK(prob_len) + { + UPDATE_0_CHECK; + prob_len = prob + LenLow + GET_LEN_STATE; + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + prob_len = prob + LenChoice2; + IF_BIT_0_CHECK(prob_len) + { + UPDATE_0_CHECK; + prob_len = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + prob_len = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(prob_len, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned pos_slot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates - 1 ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, pos_slot); + if (pos_slot >= kStartPosModelIndex) + { + unsigned num_direct_bits = ((pos_slot >> 1) - 1); + + if (pos_slot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (pos_slot & 1)) << num_direct_bits); + } + else + { + num_direct_bits -= kNumAlignBits; + do + { + NORMALIZE_CHECK; + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } while (--num_direct_bits != 0); + prob = probs + Align; + num_direct_bits = kNumAlignBits; + } + { + unsigned i = 1; + unsigned m = 1; + do + { + REV_BIT_CHECK(prob, i, m); + } while (--num_direct_bits != 0); + } + } + } + } + NORMALIZE_CHECK; + return 1; +} + +/* First LZMA-symbol is always decoded. +And it decodes new LZMA-symbols while (buf < buf_limit), but "buf" is without last normalization +Out: + Result: + 0 - OK + 1 - Error +*/ + +#ifdef LZMA2_DEC_OPT + +int LZMA_decodeReal_3(LZMA2_DCtx *p, size_t limit, const BYTE *buf_limit); + +#else + +static int LZMA_decodeReal_3(LZMA2_DCtx *p, size_t limit, const BYTE *buf_limit) +{ + LZMA2_prob *const probs = GET_PROBS; + + unsigned state = p->state; + U32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned const pb_mask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned const lc = p->prop.lc; + unsigned const lp_mask = ((unsigned)0x100 << p->prop.lp) - ((unsigned)0x100 >> lc); + + BYTE *const dic = p->dic; + size_t const dic_buf_size = p->dic_buf_size; + size_t dic_pos = p->dic_pos; + + U32 processed_pos = p->processed_pos; + U32 const check_dic_size = p->check_dic_size; + unsigned len = 0; + + const BYTE *buf = p->buf; + U32 range = p->range; + U32 code = p->code; + + do + { + LZMA2_prob *prob; + U32 bound; + unsigned ttt; + unsigned pos_state = CALC_POS_STATE(processed_pos, pb_mask); + + prob = probs + IsMatch + COMBINED_PS_STATE; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (processed_pos != 0 || check_dic_size != 0) + prob += (U32)3 * ((((processed_pos << 8) + dic[(dic_pos == 0 ? dic_buf_size : dic_pos) - 1]) & lp_mask) << lc); + processed_pos++; + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; +#ifdef LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); +#else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC +#endif + } + else + { + unsigned match_byte = dic[dic_pos - rep0 + (dic_pos < rep0 ? dic_buf_size : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; +#ifdef LZMA_SIZE_OPT + do + { + unsigned bit; + LZMA2_prob *prob_lit; + MATCHED_LITER_DEC + } while (symbol < 0x100); +#else + { + unsigned bit; + LZMA2_prob *prob_lit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } +#endif + } + + dic[dic_pos++] = (BYTE)symbol; + continue; + } + + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + /* + that case was checked before with kBadRepCode: + if (check_dic_size == 0 && processed_pos == 0) + return 1; + */ + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + COMBINED_PS_STATE; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dic_pos] = dic[dic_pos - rep0 + (dic_pos < rep0 ? dic_buf_size : 0)]; + dic_pos++; + processed_pos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + U32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; +#ifdef USE_CMOV + PREP_BIT(prob); + UPDATE_PREP_0; + UPDATE_PREP_1; + UPDATE_COND(prob); + distance = code < bound ? rep2 : rep3; + rep3 = code < bound ? rep3 : rep2; + UPDATE_CODE; +#else + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } +#endif + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + +#ifdef LZMA_SIZE_OPT + unsigned lim, offset; + LZMA2_prob *prob_len = prob + LenChoice; + IF_BIT_0(prob_len) + { + UPDATE_0(prob_len); + prob_len = prob + LenLow + GET_LEN_STATE; + offset = 0; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(prob_len); + prob_len = prob + LenChoice2; + IF_BIT_0(prob_len) + { + UPDATE_0(prob_len); + prob_len = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(prob_len); + prob_len = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + lim = (1 << kLenNumHighBits); + } + } + TREE_DECODE(prob_len, lim, len); + len += offset; +#else + LZMA2_prob *prob_len = prob + LenChoice; + IF_BIT_0(prob_len) + { + UPDATE_0(prob_len); + prob_len = prob + LenLow + GET_LEN_STATE; + len = 1; + TREE_GET_BIT(prob_len, len); + TREE_GET_BIT(prob_len, len); + TREE_GET_BIT(prob_len, len); + len -= 8; + } + else + { + UPDATE_1(prob_len); + prob_len = prob + LenChoice2; + IF_BIT_0(prob_len) + { + UPDATE_0(prob_len); + prob_len = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + len = 1; + TREE_GET_BIT(prob_len, len); + TREE_GET_BIT(prob_len, len); + TREE_GET_BIT(prob_len, len); + } + else + { + UPDATE_1(prob_len); + prob_len = prob + LenHigh; + TREE_DECODE(prob_len, (1 << kLenNumHighBits), len); + len += kLenNumLowSymbols * 2; + } + } +#endif + + if (state >= kNumStates) + { + U32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned pos_slot = (unsigned)distance; + unsigned num_direct_bits = (unsigned)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (pos_slot < kEndPosModelIndex) + { + distance <<= num_direct_bits; + prob = probs + SpecPos; + { + U32 m = 1; + distance++; + do + { + REV_BIT_VAR(prob, distance, m); + } while (--num_direct_bits); + distance -= m; + } + } + else + { + num_direct_bits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + U32 t; + code -= range; + t = (0 - ((U32)code >> 31)); + distance = (distance << 1) + (t + 1); + code += range & t; + } while (--num_direct_bits != 0); + prob = probs + Align; + distance <<= kNumAlignBits; + { + U32 i = 1; + REV_BIT_CONST(prob, i, 1); + REV_BIT_CONST(prob, i, 2); + REV_BIT_CONST(prob, i, 4); + REV_BIT_LAST(prob, i, 8); + distance |= i; + } + } + } + + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + if (distance >= (check_dic_size == 0 ? processed_pos : check_dic_size)) + { + p->dic_pos = dic_pos; + return 1; + } + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + } + + len += kMatchMinLen; + + size_t rem = limit - dic_pos; + if (rem == 0) + { + p->dic_pos = dic_pos; + return 1; + } + + unsigned cur_len = ((rem < len) ? (unsigned)rem : len); + size_t pos = dic_pos - rep0 + (dic_pos < rep0 ? dic_buf_size : 0); + + processed_pos += cur_len; + + len -= cur_len; + if (cur_len <= dic_buf_size - pos) + { + BYTE *dest = dic + dic_pos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dic_pos; + const BYTE *end = dest + cur_len; + dic_pos += cur_len; + do + *(dest) = (BYTE)*(dest + src); + while (++dest != end); + } + else + { + do + { + dic[dic_pos++] = dic[pos]; + if (++pos == dic_buf_size) + pos = 0; + } while (--cur_len != 0); + } + } while (dic_pos < limit && buf < buf_limit); + + NORMALIZE; + + p->buf = buf; + p->range = range; + p->code = code; + p->remain_len = len; + p->dic_pos = dic_pos; + p->processed_pos = processed_pos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return 0; +} + +#endif + +static void LZMA_writeRem(LZMA2_DCtx *const p, size_t const limit) +{ + if (p->remain_len != 0 && p->remain_len < kMatchSpecLenStart) + { + BYTE *const dic = p->dic; + size_t dic_pos = p->dic_pos; + size_t const dic_buf_size = p->dic_buf_size; + unsigned len = p->remain_len; + size_t const rep0 = p->reps[0]; + size_t const rem = limit - dic_pos; + if (rem < len) + len = (unsigned)(rem); + + if (p->check_dic_size == 0 && p->prop.dic_size - p->processed_pos <= len) + p->check_dic_size = p->prop.dic_size; + + p->processed_pos += len; + p->remain_len -= len; + while (len != 0) + { + len--; + dic[dic_pos] = dic[dic_pos - rep0 + (dic_pos < rep0 ? dic_buf_size : 0)]; + dic_pos++; + } + p->dic_pos = dic_pos; + } +} + +#define kRange0 0xFFFFFFFF +#define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)) +#define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))) +#if kBadRepCode != (0xC0000000 - 0x400) +#error Stop_Compiling_Bad_LZMA_Check +#endif + +static size_t LZMA_decodeReal2(LZMA2_DCtx *const p, size_t const limit, const BYTE *const buf_limit) +{ + if (p->buf == buf_limit && !LZMA_tryDummy(p)) + return FL2_ERROR(corruption_detected); + do + { + size_t limit2 = limit; + if (p->check_dic_size == 0) + { + U32 const rem = p->prop.dic_size - p->processed_pos; + if (limit - p->dic_pos > rem) + limit2 = p->dic_pos + rem; + if (p->processed_pos == 0) + if (p->code >= kBadRepCode) + return FL2_ERROR(corruption_detected); + } + + do { + if (LZMA_decodeReal_3(p, limit2, buf_limit) != 0) + return FL2_ERROR(corruption_detected); + } while (p->dic_pos < limit2 && p->buf == buf_limit && LZMA_tryDummy(p)); + + if (p->check_dic_size == 0 && p->processed_pos >= p->prop.dic_size) + p->check_dic_size = p->prop.dic_size; + + LZMA_writeRem(p, limit); + } while (p->dic_pos < limit && p->buf < buf_limit && p->remain_len < kMatchSpecLenStart); + + if (p->remain_len > kMatchSpecLenStart) + p->remain_len = kMatchSpecLenStart; + + return FL2_error_no_error; +} + + +static void LZMA_initDicAndState(LZMA2_DCtx *const p, BYTE const init_dic, BYTE const init_state) +{ + p->need_flush = 1; + p->remain_len = 0; + + if (init_dic) + { + p->processed_pos = 0; + p->check_dic_size = 0; + p->need_init_state = 1; + } + if (init_state) + p->need_init_state = 1; +} + +static void LZMA_init(LZMA2_DCtx *const p) +{ + p->dic_pos = 0; + LZMA_initDicAndState(p, 1, 1); +} + +static void LZMA_initStateReal(LZMA2_DCtx *const p) +{ + size_t const num_probs = LzmaProps_GetNumProbs(&p->prop); + LZMA2_prob *probs = p->probs; + for (size_t i = 0; i < num_probs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + p->need_init_state = 0; +} + +static size_t LZMA_decodeToDic(LZMA2_DCtx *const p, size_t const dic_limit, const BYTE *src, size_t *const src_len, + LZMA2_finishMode finish_mode) +{ + size_t in_size = *src_len; + (*src_len) = 0; + LZMA_writeRem(p, dic_limit); + + if (p->need_flush) + { + if (in_size < RC_INIT_SIZE) + { + return LZMA_STATUS_NEEDS_MORE_INPUT; + } + if (src[0] != 0) + return FL2_ERROR(corruption_detected); + p->code = + ((U32)src[1] << 24) + | ((U32)src[2] << 16) + | ((U32)src[3] << 8) + | ((U32)src[4]); + src += RC_INIT_SIZE; + (*src_len) += RC_INIT_SIZE; + in_size -= RC_INIT_SIZE; + p->range = 0xFFFFFFFF; + p->need_flush = 0; + } + + while (1) { + if (p->dic_pos >= dic_limit) + { + if (p->remain_len == 0 && p->code == 0) { + return LZMA_STATUS_FINISHED; + } + return LZMA_STATUS_NOT_FINISHED; + } + + if (p->need_init_state) + LZMA_initStateReal(p); + + const BYTE *buf_limit; + if (finish_mode == LZMA_FINISH_END) { + buf_limit = src + in_size; + } + else { + if (in_size <= LZMA_REQUIRED_INPUT_MAX) { + return LZMA_STATUS_NEEDS_MORE_INPUT; + } + buf_limit = src + in_size - LZMA_REQUIRED_INPUT_MAX; + } + p->buf = src; + + CHECK_F(LZMA_decodeReal2(p, dic_limit, buf_limit)); + + size_t const processed = (size_t)(p->buf - src); + (*src_len) += processed; + src += processed; + in_size -= processed; + } +} + +void LZMA_constructDCtx(LZMA2_DCtx *p) +{ + p->dic = NULL; + p->ext_dic = 1; + p->state2 = LZMA2_STATE_FINISHED; + p->probs_1664 = p->probs + 1664; +} + +static void LZMA_freeDict(LZMA2_DCtx *const p) +{ + if (!p->ext_dic) { + free(p->dic); + } + p->dic = NULL; +} + +void LZMA_destructDCtx(LZMA2_DCtx *const p) +{ + LZMA_freeDict(p); +} + +size_t LZMA2_getDictSizeFromProp(BYTE const dict_prop) +{ + if (dict_prop > 40) + return FL2_ERROR(corruption_detected); + + size_t const dict_size = (dict_prop == 40) + ? (size_t)-1 + : (((size_t)2 | (dict_prop & 1)) << (dict_prop / 2 + 11)); + return dict_size; +} + +static size_t LZMA2_dictBufSize(size_t const dict_size) +{ + size_t mask = ((size_t)1 << 12) - 1; + if (dict_size >= ((size_t)1 << 30)) mask = ((size_t)1 << 22) - 1; + else if (dict_size >= ((size_t)1 << 22)) mask = ((size_t)1 << 20) - 1; + + size_t dic_buf_size = ((size_t)dict_size + mask) & ~mask; + if (dic_buf_size < dict_size) + dic_buf_size = dict_size; + + return dic_buf_size; +} + +size_t LZMA2_decMemoryUsage(size_t const dict_size) +{ + return sizeof(LZMA2_DCtx) + LZMA2_dictBufSize(dict_size); +} + +size_t LZMA2_initDecoder(LZMA2_DCtx *const p, BYTE const dict_prop, BYTE *const dic, size_t dic_buf_size) +{ + size_t const dict_size = LZMA2_getDictSizeFromProp(dict_prop); + if (FL2_isError(dict_size)) + return dict_size; + + if (dic == NULL) { + dic_buf_size = LZMA2_dictBufSize(dict_size); + + if (p->dic == NULL || dic_buf_size != p->dic_buf_size) { + LZMA_freeDict(p); + p->dic = malloc(dic_buf_size); + if (p->dic == NULL) + return FL2_ERROR(memory_allocation); + p->ext_dic = 0; + } + } + else { + LZMA_freeDict(p); + p->dic = dic; + p->ext_dic = 1; + } + p->dic_buf_size = dic_buf_size; + p->prop.lc = 3; + p->prop.lp = 0; + p->prop.lc = 2; + p->prop.dic_size = (U32)dict_size; + + p->state2 = LZMA2_STATE_CONTROL; + p->need_init_dic = 1; + p->need_init_state2 = 1; + p->need_init_prop = 1; + LZMA_init(p); + return FL2_error_no_error; +} + +static void LZMA_updateWithUncompressed(LZMA2_DCtx *const p, const BYTE *const src, size_t const size) +{ + memcpy(p->dic + p->dic_pos, src, size); + p->dic_pos += size; + if (p->check_dic_size == 0 && p->prop.dic_size - p->processed_pos <= size) + p->check_dic_size = p->prop.dic_size; + p->processed_pos += (U32)size; +} + +static unsigned LZMA2_nextChunkInfo(BYTE *const control, + size_t *const unpack_size, size_t *const pack_size, + LZMA2_props *const prop, + const BYTE *const src, ptrdiff_t *const src_len) +{ + ptrdiff_t const len = *src_len; + *src_len = 0; + if (len <= 0) + return LZMA2_STATE_CONTROL; + *control = *src; + if (*control == 0) { + *src_len = 1; + return LZMA2_STATE_FINISHED; + } + if (len < 3) + return LZMA2_STATE_CONTROL; + if (LZMA2_IS_UNCOMPRESSED_STATE(*control)) { + if (*control > 2) + return LZMA2_STATE_ERROR; + *src_len = 3; + *unpack_size = (((size_t)src[1] << 8) | src[2]) + 1; + } + else { + S32 const has_prop = LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(*control)); + if (len < 5 + has_prop) + return LZMA2_STATE_CONTROL; + *src_len = 5 + has_prop; + *unpack_size = ((size_t)(*control & 0x1F) << 16) + ((size_t)src[1] << 8) + src[2] + 1; + *pack_size = ((size_t)src[3] << 8) + src[4] + 1; + if (has_prop) { + BYTE b = src[5]; + if (b >= (9 * 5 * 5)) + return LZMA2_STATE_ERROR; + BYTE const lc = b % 9; + b /= 9; + prop->pb = b / 5; + BYTE const lp = b % 5; + if (lc + lp > kLzma2LcLpMax) + return LZMA2_STATE_ERROR; + prop->lc = (BYTE)lc; + prop->lp = (BYTE)lp; + } + } + return LZMA2_STATE_DATA; +} + +static size_t LZMA2_decodeChunkToDic(LZMA2_DCtx *const p, size_t const dic_limit, + const BYTE *src, size_t *const src_len, LZMA2_finishMode const finish_mode) +{ + if (p->state2 == LZMA2_STATE_FINISHED) + return LZMA_STATUS_FINISHED; + + size_t const in_size = *src_len; + *src_len = 0; + + if (p->state2 == LZMA2_STATE_CONTROL) { + ptrdiff_t len = in_size - *src_len; + p->state2 = LZMA2_nextChunkInfo(&p->control, &p->unpack_size, &p->pack_size, &p->prop, src, &len); + + *src_len += len; + src += len; + + if (p->state2 == LZMA2_STATE_ERROR) + return FL2_ERROR(corruption_detected); + else if (p->state2 == LZMA2_STATE_CONTROL) + return LZMA_STATUS_NEEDS_MORE_INPUT; + else if (p->state2 == LZMA2_STATE_FINISHED) + return LZMA_STATUS_FINISHED; + + if (LZMA2_IS_UNCOMPRESSED_STATE(p->control)) + { + BYTE const init_dic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); + if (init_dic) + p->need_init_prop = p->need_init_state2 = 1; + else if (p->need_init_dic) + return FL2_ERROR(corruption_detected); + p->need_init_dic = 0; + LZMA_initDicAndState(p, init_dic, 0); + } + else { + unsigned const mode = LZMA2_GET_LZMA_MODE(p->control); + BYTE const init_dic = (mode == 3); + BYTE const init_state = (mode != 0); + if ((!init_dic && p->need_init_dic) || (!init_state && p->need_init_state2)) + return FL2_ERROR(corruption_detected); + + LZMA_initDicAndState(p, init_dic, init_state); + p->need_init_dic = 0; + p->need_init_state2 = 0; + } + } + if (p->state2 == LZMA2_STATE_DATA) { + size_t in_cur = in_size - *src_len; + size_t const dic_pos = p->dic_pos; + size_t out_cur = dic_limit - dic_pos; + + if (out_cur == 0) + return (finish_mode == LZMA_FINISH_ANY) ? LZMA_STATUS_OUTPUT_FULL : FL2_ERROR(dstSize_tooSmall); + + if (out_cur > p->unpack_size) + out_cur = p->unpack_size; + + if (LZMA2_IS_UNCOMPRESSED_STATE(p->control)) { + if (in_cur == 0) + return LZMA_STATUS_NEEDS_MORE_INPUT; + + if (in_cur > out_cur) + in_cur = out_cur; + + LZMA_updateWithUncompressed(p, src, in_cur); + + src += in_cur; + *src_len += in_cur; + p->unpack_size -= in_cur; + } + else + { + LZMA2_finishMode cur_finish_mode = LZMA_FINISH_END; + if (in_cur < p->pack_size) { + if (in_cur < LZMA_REQUIRED_INPUT_MAX) + return LZMA_STATUS_NEEDS_MORE_INPUT; + cur_finish_mode = LZMA_FINISH_ANY; + } + else { + in_cur = p->pack_size; + } + + size_t res = LZMA_decodeToDic(p, dic_pos + out_cur, src, &in_cur, cur_finish_mode); + + src += in_cur; + *src_len += in_cur; + p->pack_size -= in_cur; + p->unpack_size -= p->dic_pos - dic_pos; + + if (FL2_isError(res)) + return res; + + /* error if decoder not finished but chunk output is complete */ + if (res != LZMA_STATUS_FINISHED && p->unpack_size == 0) + return FL2_ERROR(corruption_detected); + + /* Error conditions: + 1. need input but chunk is finished + 2. have output space, input not needed, but nothing was written*/ + if (res == LZMA_STATUS_NEEDS_MORE_INPUT) + return (p->pack_size == 0) ? FL2_ERROR(corruption_detected) : res; + else if (in_cur == 0 && p->dic_pos == dic_pos) + return FL2_ERROR(corruption_detected); + } + + if (p->unpack_size == 0) + p->state2 = LZMA2_STATE_CONTROL; + } + return LZMA_STATUS_NOT_FINISHED; +} + +size_t LZMA2_decodeToDic(LZMA2_DCtx *const p, size_t const dic_limit, + const BYTE *const src, size_t *const src_len, LZMA2_finishMode const finish_mode) +{ + if (p->state2 == LZMA2_STATE_ERROR) + return FL2_ERROR(corruption_detected); + + size_t const in_size = *src_len; + size_t in_pos = 0; + size_t res; + + do { + size_t len = in_size - in_pos; + res = LZMA2_decodeChunkToDic(p, dic_limit, src + in_pos, &len, finish_mode); + in_pos += len; + if (FL2_isError(res)) { + p->state2 = LZMA2_STATE_ERROR; + break; + } + } while (res != LZMA_STATUS_FINISHED + && res != LZMA_STATUS_NEEDS_MORE_INPUT + && res != LZMA_STATUS_OUTPUT_FULL); + + *src_len = in_pos; + return res; +} + +#if 0 +size_t LZMA2_decodeToDic(LZMA2_DCtx *const p, size_t const dic_limit, + const BYTE *src, size_t *const src_len, LZMA2_finishMode const finish_mode) +{ + size_t const in_size = *src_len; + size_t res = FL2_error_no_error; + *src_len = 0; + + while (p->state2 != LZMA2_STATE_ERROR) + { + if(p->state2 == LZMA2_STATE_CONTROL) { + ptrdiff_t len = in_size - *src_len; + p->state2 = LZMA2_nextChunkInfo(&p->control, &p->unpack_size, &p->pack_size, &p->prop, src, &len); + *src_len += len; + src += len; + } + + if (p->state2 == LZMA2_STATE_FINISHED) + return LZMA_STATUS_FINISHED; + + size_t const dic_pos = p->dic_pos; + + if (dic_pos == dic_limit && finish_mode == LZMA_FINISH_ANY) + return LZMA_STATUS_NOT_FINISHED; + + if (p->state2 != LZMA2_STATE_DATA && p->state2 != LZMA2_STATE_DATA_CONT) + { + if (p->state2 == LZMA2_STATE_CONTROL) + { + return LZMA_STATUS_NEEDS_MORE_INPUT; + } + break; + } + + size_t in_cur = in_size - *src_len; + size_t out_cur = dic_limit - dic_pos; + LZMA2_finishMode cur_finish_mode = LZMA_FINISH_ANY; + + if (out_cur >= p->unpack_size) + { + out_cur = (size_t)p->unpack_size; + } + if (in_cur >= p->pack_size) + cur_finish_mode = LZMA_FINISH_END; + + if (LZMA2_IS_UNCOMPRESSED_STATE(p->control)) + { + if (in_cur == 0) + { + return LZMA_STATUS_NEEDS_MORE_INPUT; + } + + if (p->state2 == LZMA2_STATE_DATA) + { + BYTE const init_dic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); + if (init_dic) + p->need_init_prop = p->need_init_state2 = 1; + else if (p->need_init_dic) + break; + p->need_init_dic = 0; + LZMA_initDicAndState(p, init_dic, 0); + } + + if (in_cur > out_cur) + in_cur = out_cur; + if (in_cur == 0) + break; + + LZMA_updateWithUncompressed(p, src, in_cur); + + src += in_cur; + *src_len += in_cur; + p->unpack_size -= (U32)in_cur; + p->state2 = (p->unpack_size == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT; + } + else + { + if (p->state2 == LZMA2_STATE_DATA) + { + unsigned const mode = LZMA2_GET_LZMA_MODE(p->control); + BYTE const init_dic = (mode == 3); + BYTE const init_state = (mode != 0); + if ((!init_dic && p->need_init_dic) || (!init_state && p->need_init_state2)) + break; + + LZMA_initDicAndState(p, init_dic, init_state); + p->need_init_dic = 0; + p->need_init_state2 = 0; + p->state2 = LZMA2_STATE_DATA_CONT; + } + + if (in_cur > p->pack_size) + in_cur = (size_t)p->pack_size; + + res = LZMA_decodeToDic(p, dic_pos + out_cur, src, &in_cur, cur_finish_mode); + + src += in_cur; + *src_len += in_cur; + p->pack_size -= (U32)in_cur; + out_cur = p->dic_pos - dic_pos; + p->unpack_size -= (U32)out_cur; + + if (FL2_isError(res)) + break; + + if (res == LZMA_STATUS_NEEDS_MORE_INPUT) + { + if (p->pack_size == 0) + break; + return res; + } + + if (p->pack_size == 0 && p->unpack_size == 0) + { + if (res != LZMA_STATUS_FINISHED) + break; + p->state2 = LZMA2_STATE_CONTROL; + } + else if (in_cur == 0 && out_cur == 0) + { + break; + } + + } + } + + p->state2 = LZMA2_STATE_ERROR; + if (FL2_isError(res)) + return res; + return FL2_ERROR(corruption_detected); +} +#endif + +size_t LZMA2_decodeToBuf(LZMA2_DCtx *const p, BYTE *dest, size_t *const dest_len, const BYTE *src, size_t *const src_len, LZMA2_finishMode const finish_mode) +{ + size_t out_size = *dest_len, in_size = *src_len; + *src_len = *dest_len = 0; + + for (;;) + { + if (p->dic_pos == p->dic_buf_size) + p->dic_pos = 0; + + size_t const dic_pos = p->dic_pos; + LZMA2_finishMode cur_finish_mode = LZMA_FINISH_ANY; + size_t out_cur = p->dic_buf_size - dic_pos; + + if (out_cur >= out_size) { + out_cur = out_size; + cur_finish_mode = finish_mode; + } + + size_t in_cur = in_size; + size_t const res = LZMA2_decodeToDic(p, dic_pos + out_cur, src, &in_cur, cur_finish_mode); + + src += in_cur; + in_size -= in_cur; + *src_len += in_cur; + out_cur = p->dic_pos - dic_pos; + memcpy(dest, p->dic + dic_pos, out_cur); + dest += out_cur; + out_size -= out_cur; + *dest_len += out_cur; + if (FL2_isError(res) || res == LZMA_STATUS_FINISHED) + return res; + if (out_cur == 0 || out_size == 0) + return FL2_error_no_error; + } +} + +U64 LZMA2_getUnpackSize(const BYTE *const src, size_t const src_len) +{ + U64 unpack_total = 0; + size_t pos = 1; + while (pos < src_len) { + LZMA2_chunk inf; + int const type = LZMA2_parseInput(src, pos, src_len - pos, &inf); + if (type == CHUNK_FINAL) + return unpack_total; + pos += inf.pack_size; + if (type == CHUNK_ERROR || type == CHUNK_MORE_DATA) + break; + unpack_total += inf.unpack_size; + } + return LZMA2_CONTENTSIZE_ERROR; +} + +LZMA2_parseRes LZMA2_parseInput(const BYTE* const in_buf, size_t const pos, ptrdiff_t const len, LZMA2_chunk *const inf) +{ + inf->pack_size = 0; + inf->unpack_size = 0; + + if (len <= 0) + return CHUNK_ERROR; + + BYTE const control = in_buf[pos]; + if (control == 0) { + inf->pack_size = 1; + return CHUNK_FINAL; + } + if (len < 3) + return CHUNK_MORE_DATA; + if (LZMA2_IS_UNCOMPRESSED_STATE(control)) { + if (control > 2) + return CHUNK_ERROR; + inf->unpack_size = (((U32)in_buf[pos + 1] << 8) | in_buf[pos + 2]) + 1; + inf->pack_size = 3 + inf->unpack_size; + } + else { + S32 const has_prop = LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(control)); + if (len < 5 + has_prop) + return CHUNK_MORE_DATA; + inf->unpack_size = ((U32)(control & 0x1F) << 16) + ((U32)in_buf[pos + 1] << 8) + in_buf[pos + 2] + 1; + inf->pack_size = 5 + has_prop + ((U32)in_buf[pos + 3] << 8) + in_buf[pos + 4] + 1; + if (LZMA2_GET_LZMA_MODE(control) == 3) + return CHUNK_DICT_RESET; + } + return CHUNK_CONTINUE; +} diff --git a/builtins/flzma2/lzma2_dec.h b/builtins/flzma2/lzma2_dec.h new file mode 100644 index 0000000000000..ba0c2e398d151 --- /dev/null +++ b/builtins/flzma2/lzma2_dec.h @@ -0,0 +1,224 @@ +/* lzma2_dec.h -- LZMA2 Decoder +2017-04-03 : Igor Pavlov : Public domain +Modified for FL2 by Conor McCarthy */ + +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +#include "mem.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* #define LZMA_DEC_PROB16 */ +/* 32-bit probs can increase the speed on some CPUs, + but memory usage for LZMA2_DCtx::probs will be doubled in that case */ + +#ifdef LZMA_DEC_PROB16 +typedef U16 LZMA2_prob; +#else +typedef U32 LZMA2_prob; +#endif + + +/* ---------- LZMA Properties ---------- */ + +typedef struct +{ + BYTE lc; + BYTE lp; + BYTE pb; + BYTE pad_; + U32 dic_size; +} LZMA2_props; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenLow 0 +#define LenHigh (LenLow + 2 * (kNumPosStatesMax << kLenNumLowBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + +#define LenChoice LenLow +#define LenChoice2 (LenLow + (1 << kLenNumLowBits)) + +#define kNumStates 12 +#define kNumStates2 16 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols * 2 + kLenNumHighSymbols) + +/* External ASM code needs same CLzmaProb array layout. So don't change it. */ + +/* (probs_1664) is faster and better for code size at some platforms */ +/* +#ifdef MY_CPU_X86_OR_AMD64 +*/ +#define kStartOffset 1664 +#define GET_PROBS p->probs_1664 +/* +#define GET_PROBS p->probs + kStartOffset +#else +#define kStartOffset 0 +#define GET_PROBS p->probs +#endif +*/ + +#define SpecPos (-kStartOffset) +#define IsRep0Long (SpecPos + kNumFullDistances) +#define RepLenCoder (IsRep0Long + (kNumStates2 << kNumPosBitsMax)) +#define LenCoder (RepLenCoder + kNumLenProbs) +#define IsMatch (LenCoder + kNumLenProbs) +#define Align (IsMatch + (kNumStates2 << kNumPosBitsMax)) +#define IsRep (Align + kAlignTableSize) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define PosSlot (IsRepG2 + kNumStates) +#define Literal (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define NUM_BASE_PROBS (Literal + kStartOffset) + +#if Align != 0 && kStartOffset != 0 +#error Stop_Compiling_Bad_LZMA_kAlign +#endif + +#if NUM_BASE_PROBS != 1984 +#error Stop_Compiling_Bad_LZMA_PROBS +#endif + + +#define kLzmaLitSize 0x300 + +#define LzmaProps_GetNumProbs(p) (NUM_BASE_PROBS + ((U32)kLzmaLitSize << ((p)->lc + (p)->lp))) + + +#define CALC_POS_STATE(processed_pos, pb_mask) (((processed_pos) & (pb_mask)) << 4) +#define COMBINED_PS_STATE (pos_state + state) +#define GET_LEN_STATE (pos_state) + +#define kLzma2LcLpMax 4U + + +typedef struct +{ + LZMA2_props prop; + BYTE *dic; + size_t dic_pos; + size_t dic_buf_size; + const BYTE *buf; + LZMA2_prob *probs_1664; + U32 range; + U32 code; + U32 processed_pos; + U32 check_dic_size; + U32 reps[4]; + unsigned state; + unsigned state2; + unsigned remain_len; + size_t pack_size; + size_t unpack_size; + BYTE control; + BYTE need_init_dic; + BYTE need_init_state; + BYTE need_init_state2; + BYTE need_init_prop; + BYTE need_flush; + BYTE ext_dic; + BYTE pad_; + LZMA2_prob probs[NUM_BASE_PROBS + ((U32)kLzmaLitSize << kLzma2LcLpMax)]; +} LZMA2_DCtx; + +void LZMA_constructDCtx(LZMA2_DCtx *p); + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} LZMA2_finishMode; + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED, /* stream was finished */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_OUTPUT_FULL /* not finished; output buffer is full */ +} LZMA2_status; + +void LZMA_destructDCtx(LZMA2_DCtx *const p); + +size_t LZMA2_getDictSizeFromProp(BYTE const dict_prop); + +#define LZMA2_CONTENTSIZE_ERROR (size_t)-1 + +U64 LZMA2_getUnpackSize(const BYTE *const src, size_t const src_len); + +size_t LZMA2_decMemoryUsage(size_t const dict_size); + +size_t LZMA2_initDecoder(LZMA2_DCtx *const p, BYTE const dict_prop, BYTE *const dic, size_t dic_buf_size); + +size_t LZMA2_decodeToDic(LZMA2_DCtx *const p, size_t const dic_limit, + const BYTE *const src, size_t *const src_len, LZMA2_finishMode const finish_mode); + +size_t LZMA2_decodeToBuf(LZMA2_DCtx *const p, BYTE *dest, size_t *const dest_len, + const BYTE *src, size_t *const src_len, LZMA2_finishMode const finish_mode); + +typedef enum +{ + CHUNK_MORE_DATA, + CHUNK_CONTINUE, + CHUNK_DICT_RESET, + CHUNK_FINAL, + CHUNK_ERROR +} LZMA2_parseRes; + +typedef struct +{ + size_t pack_size; + size_t unpack_size; +} LZMA2_chunk; + +#if defined(FL2_DEBUG) && (FL2_DEBUG>=1) +# define LZMA2_MT_INPUT_SIZE 0x400 +#else +# define LZMA2_MT_INPUT_SIZE 0x40000 +#endif + +LZMA2_parseRes LZMA2_parseInput(const BYTE* const in_buf, size_t const pos, ptrdiff_t const len, LZMA2_chunk *const inf); + +#if defined (__cplusplus) +} +#endif + +#endif diff --git a/builtins/flzma2/lzma2_enc.c b/builtins/flzma2/lzma2_enc.c new file mode 100644 index 0000000000000..efad05644caca --- /dev/null +++ b/builtins/flzma2/lzma2_enc.c @@ -0,0 +1,2099 @@ +/* lzma2_enc.c -- LZMA2 Encoder +Based on LzmaEnc.c and Lzma2Enc.c : Igor Pavlov +Modified for FL2 by Conor McCarthy +Public domain +*/ + +#include +#include + +#include "fl2_errors.h" +#include "fl2_internal.h" +#include "lzma2_enc.h" +#include "fl2_compress_internal.h" +#include "mem.h" +#include "count.h" +#include "radix_mf.h" +#include "range_enc.h" + +#ifdef FL2_XZ_BUILD +# include "tuklib_integer.h" +# define MEM_readLE32(a) unaligned_read32le(a) + +# ifdef TUKLIB_FAST_UNALIGNED_ACCESS +# define MEM_read16(a) (*(const U16*)(a)) +# endif + +#endif + +#define kNumReps 4U +#define kNumStates 12U + +#define kNumLiterals 0x100U +#define kNumLitTables 3U + +#define kNumLenToPosStates 4U +#define kNumPosSlotBits 6U +#define kDicLogSizeMin 18U +#define kDicLogSizeMax 31U +#define kDistTableSizeMax (kDicLogSizeMax * 2U) + +#define kNumAlignBits 4U +#define kAlignTableSize (1U << kNumAlignBits) +#define kAlignMask (kAlignTableSize - 1U) +#define kMatchRepriceFrequency 64U +#define kRepLenRepriceFrequency 64U + +#define kStartPosModelIndex 4U +#define kEndPosModelIndex 14U +#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex) + +#define kNumFullDistancesBits (kEndPosModelIndex >> 1U) +#define kNumFullDistances (1U << kNumFullDistancesBits) + +#define kNumPositionBitsMax 4U +#define kNumPositionStatesMax (1U << kNumPositionBitsMax) +#define kNumLiteralContextBitsMax 4U +#define kNumLiteralPosBitsMax 4U +#define kLcLpMax 4U + + +#define kLenNumLowBits 3U +#define kLenNumLowSymbols (1U << kLenNumLowBits) +#define kLenNumHighBits 8U +#define kLenNumHighSymbols (1U << kLenNumHighBits) + +#define kLenNumSymbolsTotal (kLenNumLowSymbols * 2 + kLenNumHighSymbols) + +#define kMatchLenMin 2U +#define kMatchLenMax (kMatchLenMin + kLenNumSymbolsTotal - 1U) + +#define kMatchesMax 65U /* Doesn't need to be larger than FL2_HYBRIDCYCLES_MAX + 1 */ + +#define kOptimizerEndSize 32U +#define kOptimizerBufferSize (kMatchLenMax * 2U + kOptimizerEndSize) +#define kOptimizerSkipSize 16U +#define kInfinityPrice (1UL << 30U) +#define kNullDist (U32)-1 + +#define kMaxMatchEncodeSize 20 + +#define kMaxChunkCompressedSize (1UL << 16U) +/* Need to leave sufficient space for expanded output from a full opt buffer with bad starting probs */ +#define kChunkSize (kMaxChunkCompressedSize - 2048U) +#define kSqrtChunkSize 252U + +/* Hard to define where the match table read pos definitely catches up with the output size, but + * 64 bytes of input expanding beyond 256 bytes right after an encoder reset is most likely impossible. + * The encoder will error out if this happens. */ +#define kTempMinOutput 256U +#define kTempBufferSize (kTempMinOutput + kOptimizerBufferSize + kOptimizerBufferSize / 4U) + +#define kMaxChunkUncompressedSize (1UL << 21U) + +#define kChunkHeaderSize 5U +#define kChunkResetShift 5U +#define kChunkUncompressedDictReset 1U +#define kChunkUncompressed 2U +#define kChunkCompressedFlag 0x80U +#define kChunkNothingReset 0U +#define kChunkStateReset (1U << kChunkResetShift) +#define kChunkStatePropertiesReset (2U << kChunkResetShift) +#define kChunkAllReset (3U << kChunkResetShift) + +#define kMaxHashDictBits 14U +#define kHash3Bits 14U +#define kNullLink -1 + +#define kMinTestChunkSize 0x4000U +#define kRandomFilterMarginBits 8U + +#define kState_LitAfterMatch 4 +#define kState_LitAfterRep 5 +#define kState_MatchAfterLit 7 +#define kState_RepAfterLit 8 + +static const BYTE kLiteralNextStates[kNumStates] = { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5 }; +#define LIT_NEXT_STATE(s) kLiteralNextStates[s] +static const BYTE kMatchNextStates[kNumStates] = { 7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10 }; +#define MATCH_NEXT_STATE(s) kMatchNextStates[s] +static const BYTE kRepNextStates[kNumStates] = { 8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11 }; +#define REP_NEXT_STATE(s) kRepNextStates[s] +static const BYTE kShortRepNextStates[kNumStates] = { 9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11 }; +#define SHORT_REP_NEXT_STATE(s) kShortRepNextStates[s] + +#include "fastpos_table.h" +#include "radix_get.h" + +/* Probabilities and prices for encoding match lengths. + * Two objects of this type are needed, one for normal matches + * and another for rep matches. + */ +typedef struct +{ + size_t table_size; + unsigned prices[kNumPositionStatesMax][kLenNumSymbolsTotal]; + LZMA2_prob choice; /* low[0] is choice_2. Must be consecutive for speed */ + LZMA2_prob low[kNumPositionStatesMax << (kLenNumLowBits + 1)]; + LZMA2_prob high[kLenNumHighSymbols]; +} LZMA2_lenStates; + +/* All probabilities for the encoder. This is a separate from the encoder object + * so the state can be saved and restored in case a chunk is not compressible. + */ +typedef struct +{ + /* Fields are ordered for speed */ + LZMA2_lenStates rep_len_states; + LZMA2_prob is_rep0_long[kNumStates][kNumPositionStatesMax]; + + size_t state; + U32 reps[kNumReps]; + + LZMA2_prob is_match[kNumStates][kNumPositionStatesMax]; + LZMA2_prob is_rep[kNumStates]; + LZMA2_prob is_rep_G0[kNumStates]; + LZMA2_prob is_rep_G1[kNumStates]; + LZMA2_prob is_rep_G2[kNumStates]; + + LZMA2_lenStates len_states; + + LZMA2_prob dist_slot_encoders[kNumLenToPosStates][1 << kNumPosSlotBits]; + LZMA2_prob dist_align_encoders[1 << kNumAlignBits]; + LZMA2_prob dist_encoders[kNumFullDistances - kEndPosModelIndex]; + + LZMA2_prob literal_probs[(kNumLiterals * kNumLitTables) << kLcLpMax]; +} LZMA2_encStates; + +/* + * Linked list item for optimal parsing + */ +typedef struct +{ + size_t state; + U32 price; + unsigned extra; /* 0 : normal + * 1 : LIT : MATCH + * > 1 : MATCH (extra-1) : LIT : REP0 (len) */ + unsigned len; + U32 dist; + U32 reps[kNumReps]; +} LZMA2_node; + +#define MARK_LITERAL(node) (node).dist = kNullDist; (node).extra = 0; +#define MARK_SHORT_REP(node) (node).dist = 0; (node).extra = 0; + +/* + * Table and chain for 3-byte hash. Extra elements in hash_chain_3 are malloced. + */ +typedef struct { + S32 table_3[1 << kHash3Bits]; + S32 hash_chain_3[1]; +} LZMA2_hc3; + +/* + * LZMA2 encoder. + */ +struct LZMA2_ECtx_s +{ + unsigned lc; + unsigned lp; + unsigned pb; + unsigned fast_length; + size_t len_end_max; + size_t lit_pos_mask; + size_t pos_mask; + unsigned match_cycles; + FL2_strategy strategy; + + RC_encoder rc; + /* Finish writing the chunk at this size */ + size_t chunk_size; + /* Don't encode a symbol beyond this limit (used by fast mode) */ + size_t chunk_limit; + + LZMA2_encStates states; + + unsigned match_price_count; + unsigned rep_len_price_count; + size_t dist_price_table_size; + unsigned align_prices[kAlignTableSize]; + unsigned dist_slot_prices[kNumLenToPosStates][kDistTableSizeMax]; + unsigned distance_prices[kNumLenToPosStates][kNumFullDistances]; + + RMF_match base_match; /* Allows access to matches[-1] in LZMA_optimalParse */ + RMF_match matches[kMatchesMax]; + size_t match_count; + + LZMA2_node opt_buf[kOptimizerBufferSize]; + + LZMA2_hc3* hash_buf; + ptrdiff_t chain_mask_2; + ptrdiff_t chain_mask_3; + ptrdiff_t hash_dict_3; + ptrdiff_t hash_prev_index; + ptrdiff_t hash_alloc_3; + + /* Temp output buffer before space frees up in the match table */ + BYTE out_buf[kTempBufferSize]; +}; + +LZMA2_ECtx* LZMA2_createECtx(void) +{ + LZMA2_ECtx *const enc = malloc(sizeof(LZMA2_ECtx)); + DEBUGLOG(3, "LZMA2_createECtx"); + if (enc == NULL) + return NULL; + + enc->lc = 3; + enc->lp = 0; + enc->pb = 2; + enc->fast_length = 48; + enc->len_end_max = kOptimizerBufferSize - 1; + enc->lit_pos_mask = (1 << enc->lp) - 1; + enc->pos_mask = (1 << enc->pb) - 1; + enc->match_cycles = 1; + enc->strategy = FL2_ultra; + enc->match_price_count = 0; + enc->rep_len_price_count = 0; + enc->dist_price_table_size = kDistTableSizeMax; + enc->hash_buf = NULL; + enc->hash_dict_3 = 0; + enc->chain_mask_3 = 0; + enc->hash_alloc_3 = 0; + return enc; +} + +void LZMA2_freeECtx(LZMA2_ECtx *const enc) +{ + if (enc == NULL) + return; + free(enc->hash_buf); + free(enc); +} + +#define LITERAL_PROBS(enc, pos, prev_symbol) (enc->states.literal_probs + ((((pos) & enc->lit_pos_mask) << enc->lc) + ((prev_symbol) >> (8 - enc->lc))) * kNumLiterals * kNumLitTables) + +#define LEN_TO_DIST_STATE(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1) + +#define IS_LIT_STATE(state) ((state) < 7) + +HINT_INLINE +unsigned LZMA_getRepLen1Price(LZMA2_ECtx* const enc, size_t const state, size_t const pos_state) +{ + unsigned const rep_G0_prob = enc->states.is_rep_G0[state]; + unsigned const rep0_long_prob = enc->states.is_rep0_long[state][pos_state]; + return GET_PRICE_0(rep_G0_prob) + GET_PRICE_0(rep0_long_prob); +} + +static unsigned LZMA_getRepPrice(LZMA2_ECtx* const enc, size_t const rep_index, size_t const state, size_t const pos_state) +{ + unsigned price; + unsigned const rep_G0_prob = enc->states.is_rep_G0[state]; + if (rep_index == 0) { + unsigned const rep0_long_prob = enc->states.is_rep0_long[state][pos_state]; + price = GET_PRICE_0(rep_G0_prob); + price += GET_PRICE_1(rep0_long_prob); + } + else { + unsigned const rep_G1_prob = enc->states.is_rep_G1[state]; + price = GET_PRICE_1(rep_G0_prob); + if (rep_index == 1) { + price += GET_PRICE_0(rep_G1_prob); + } + else { + unsigned const rep_G2_prob = enc->states.is_rep_G2[state]; + price += GET_PRICE_1(rep_G1_prob); + price += GET_PRICE(rep_G2_prob, rep_index - 2); + } + } + return price; +} + +static unsigned LZMA_getRepMatch0Price(LZMA2_ECtx *const enc, size_t const len, size_t const state, size_t const pos_state) +{ + unsigned const rep_G0_prob = enc->states.is_rep_G0[state]; + unsigned const rep0_long_prob = enc->states.is_rep0_long[state][pos_state]; + return enc->states.rep_len_states.prices[pos_state][len - kMatchLenMin] + + GET_PRICE_0(rep_G0_prob) + + GET_PRICE_1(rep0_long_prob); +} + +static unsigned LZMA_getLiteralPriceMatched(const LZMA2_prob *const prob_table, U32 symbol, unsigned match_byte) +{ + unsigned price = 0; + unsigned offs = 0x100; + symbol |= 0x100; + do { + match_byte <<= 1; + price += GET_PRICE(prob_table[offs + (match_byte & offs) + (symbol >> 8)], (symbol >> 7) & 1); + symbol <<= 1; + offs &= ~(match_byte ^ symbol); + } while (symbol < 0x10000); + return price; +} + +HINT_INLINE +void LZMA_encodeLiteral(LZMA2_ECtx *const enc, size_t const pos, U32 symbol, unsigned const prev_symbol) +{ + RC_encodeBit0(&enc->rc, &enc->states.is_match[enc->states.state][pos & enc->pos_mask]); + enc->states.state = LIT_NEXT_STATE(enc->states.state); + + LZMA2_prob* const prob_table = LITERAL_PROBS(enc, pos, prev_symbol); + symbol |= 0x100; + do { + RC_encodeBit(&enc->rc, prob_table + (symbol >> 8), symbol & (1 << 7)); + symbol <<= 1; + } while (symbol < 0x10000); +} + +HINT_INLINE +void LZMA_encodeLiteralMatched(LZMA2_ECtx *const enc, const BYTE* const data_block, size_t const pos, U32 symbol) +{ + RC_encodeBit0(&enc->rc, &enc->states.is_match[enc->states.state][pos & enc->pos_mask]); + enc->states.state = LIT_NEXT_STATE(enc->states.state); + + unsigned match_symbol = data_block[pos - enc->states.reps[0] - 1]; + LZMA2_prob* const prob_table = LITERAL_PROBS(enc, pos, data_block[pos - 1]); + unsigned offset = 0x100; + symbol |= 0x100; + do { + match_symbol <<= 1; + size_t prob_index = offset + (match_symbol & offset) + (symbol >> 8); + RC_encodeBit(&enc->rc, prob_table + prob_index, symbol & (1 << 7)); + symbol <<= 1; + offset &= ~(match_symbol ^ symbol); + } while (symbol < 0x10000); +} + +HINT_INLINE +void LZMA_encodeLiteralBuf(LZMA2_ECtx *const enc, const BYTE* const data_block, size_t const pos) +{ + U32 const symbol = data_block[pos]; + if (IS_LIT_STATE(enc->states.state)) { + unsigned const prev_symbol = data_block[pos - 1]; + LZMA_encodeLiteral(enc, pos, symbol, prev_symbol); + } + else { + LZMA_encodeLiteralMatched(enc, data_block, pos, symbol); + } +} + +static void LZMA_lengthStates_SetPrices(const LZMA2_prob *probs, U32 start_price, unsigned *prices) +{ + for (size_t i = 0; i < 8; i += 2) { + U32 prob = probs[4 + (i >> 1)]; + U32 price = start_price + GET_PRICE(probs[1], (i >> 2)) + + GET_PRICE(probs[2 + (i >> 2)], (i >> 1) & 1); + prices[i] = price + GET_PRICE_0(prob); + prices[i + 1] = price + GET_PRICE_1(prob); + } +} + +FORCE_NOINLINE +static void LZMA_lengthStates_updatePrices(LZMA2_ECtx *const enc, LZMA2_lenStates* const ls) +{ + U32 b; + + { + unsigned const prob = ls->choice; + U32 a, c; + b = GET_PRICE_1(prob); + a = GET_PRICE_0(prob); + c = b + GET_PRICE_0(ls->low[0]); + for (size_t pos_state = 0; pos_state <= enc->pos_mask; pos_state++) { + unsigned *const prices = ls->prices[pos_state]; + const LZMA2_prob *const probs = ls->low + (pos_state << (1 + kLenNumLowBits)); + LZMA_lengthStates_SetPrices(probs, a, prices); + LZMA_lengthStates_SetPrices(probs + kLenNumLowSymbols, c, prices + kLenNumLowSymbols); + } + } + + size_t i = ls->table_size; + + if (i > kLenNumLowSymbols * 2) { + const LZMA2_prob *const probs = ls->high; + unsigned *const prices = ls->prices[0] + kLenNumLowSymbols * 2; + i = (i - (kLenNumLowSymbols * 2 - 1)) >> 1; + b += GET_PRICE_1(ls->low[0]); + do { + --i; + size_t sym = i + (1 << (kLenNumHighBits - 1)); + U32 price = b; + do { + size_t bit = sym & 1; + sym >>= 1; + price += GET_PRICE(probs[sym], bit); + } while (sym >= 2); + + unsigned const prob = probs[i + (1 << (kLenNumHighBits - 1))]; + prices[i * 2] = price + GET_PRICE_0(prob); + prices[i * 2 + 1] = price + GET_PRICE_1(prob); + } while (i); + + size_t const size = (ls->table_size - kLenNumLowSymbols * 2) * sizeof(ls->prices[0][0]); + for (size_t pos_state = 1; pos_state <= enc->pos_mask; pos_state++) + memcpy(ls->prices[pos_state] + kLenNumLowSymbols * 2, ls->prices[0] + kLenNumLowSymbols * 2, size); + } +} + +/* Rare enough that not inlining is faster overall */ +FORCE_NOINLINE +static void LZMA_encodeLength_MidHigh(LZMA2_ECtx *const enc, LZMA2_lenStates* const len_prob_table, unsigned const len, size_t const pos_state) +{ + RC_encodeBit1(&enc->rc, &len_prob_table->choice); + if (len < kLenNumLowSymbols * 2) { + RC_encodeBit0(&enc->rc, &len_prob_table->low[0]); + RC_encodeBitTree(&enc->rc, len_prob_table->low + kLenNumLowSymbols + (pos_state << (1 + kLenNumLowBits)), kLenNumLowBits, len - kLenNumLowSymbols); + } + else { + RC_encodeBit1(&enc->rc, &len_prob_table->low[0]); + RC_encodeBitTree(&enc->rc, len_prob_table->high, kLenNumHighBits, len - kLenNumLowSymbols * 2); + } +} + +HINT_INLINE +void LZMA_encodeLength(LZMA2_ECtx *const enc, LZMA2_lenStates* const len_prob_table, unsigned len, size_t const pos_state) +{ + len -= kMatchLenMin; + if (len < kLenNumLowSymbols) { + RC_encodeBit0(&enc->rc, &len_prob_table->choice); + RC_encodeBitTree(&enc->rc, len_prob_table->low + (pos_state << (1 + kLenNumLowBits)), kLenNumLowBits, len); + } + else { + LZMA_encodeLength_MidHigh(enc, len_prob_table, len, pos_state); + } +} + +FORCE_NOINLINE +static void LZMA_encodeRepMatchShort(LZMA2_ECtx *const enc, size_t const pos_state) +{ + DEBUGLOG(7, "LZMA_encodeRepMatchShort"); + RC_encodeBit1(&enc->rc, &enc->states.is_match[enc->states.state][pos_state]); + RC_encodeBit1(&enc->rc, &enc->states.is_rep[enc->states.state]); + RC_encodeBit0(&enc->rc, &enc->states.is_rep_G0[enc->states.state]); + RC_encodeBit0(&enc->rc, &enc->states.is_rep0_long[enc->states.state][pos_state]); + enc->states.state = SHORT_REP_NEXT_STATE(enc->states.state); +} + +FORCE_NOINLINE +static void LZMA_encodeRepMatchLong(LZMA2_ECtx *const enc, unsigned const len, unsigned const rep, size_t const pos_state) +{ + DEBUGLOG(7, "LZMA_encodeRepMatchLong : length %u, rep %u", len, rep); + RC_encodeBit1(&enc->rc, &enc->states.is_match[enc->states.state][pos_state]); + RC_encodeBit1(&enc->rc, &enc->states.is_rep[enc->states.state]); + if (rep == 0) { + RC_encodeBit0(&enc->rc, &enc->states.is_rep_G0[enc->states.state]); + RC_encodeBit1(&enc->rc, &enc->states.is_rep0_long[enc->states.state][pos_state]); + } + else { + U32 const distance = enc->states.reps[rep]; + RC_encodeBit1(&enc->rc, &enc->states.is_rep_G0[enc->states.state]); + if (rep == 1) { + RC_encodeBit0(&enc->rc, &enc->states.is_rep_G1[enc->states.state]); + } + else { + RC_encodeBit1(&enc->rc, &enc->states.is_rep_G1[enc->states.state]); + RC_encodeBit(&enc->rc, &enc->states.is_rep_G2[enc->states.state], rep - 2); + if (rep == 3) + enc->states.reps[3] = enc->states.reps[2]; + enc->states.reps[2] = enc->states.reps[1]; + } + enc->states.reps[1] = enc->states.reps[0]; + enc->states.reps[0] = distance; + } + LZMA_encodeLength(enc, &enc->states.rep_len_states, len, pos_state); + enc->states.state = REP_NEXT_STATE(enc->states.state); + ++enc->rep_len_price_count; +} + + +/* + * Distance slot functions based on fastpos.h from XZ + */ + +HINT_INLINE +unsigned LZMA_fastDistShift(unsigned const n) +{ + return n * (kFastDistBits - 1); +} + +HINT_INLINE +unsigned LZMA_fastDistResult(U32 const dist, unsigned const n) +{ + return distance_table[dist >> LZMA_fastDistShift(n)] + + 2 * LZMA_fastDistShift(n); +} + +static size_t LZMA_getDistSlot(U32 const distance) +{ + U32 limit = 1UL << kFastDistBits; + /* If it is small enough, we can pick the result directly from */ + /* the precalculated table. */ + if (distance < limit) { + return distance_table[distance]; + } + limit <<= LZMA_fastDistShift(1); + if (distance < limit) { + return LZMA_fastDistResult(distance, 1); + } + return LZMA_fastDistResult(distance, 2); +} + +/* * */ + + +HINT_INLINE +void LZMA_encodeNormalMatch(LZMA2_ECtx *const enc, unsigned const len, U32 const dist, size_t const pos_state) +{ + DEBUGLOG(7, "LZMA_encodeNormalMatch : length %u, dist %u", len, dist); + RC_encodeBit1(&enc->rc, &enc->states.is_match[enc->states.state][pos_state]); + RC_encodeBit0(&enc->rc, &enc->states.is_rep[enc->states.state]); + enc->states.state = MATCH_NEXT_STATE(enc->states.state); + + LZMA_encodeLength(enc, &enc->states.len_states, len, pos_state); + + size_t const dist_slot = LZMA_getDistSlot(dist); + RC_encodeBitTree(&enc->rc, enc->states.dist_slot_encoders[LEN_TO_DIST_STATE(len)], kNumPosSlotBits, (unsigned)dist_slot); + if (dist_slot >= kStartPosModelIndex) { + unsigned const footer_bits = ((unsigned)(dist_slot >> 1) - 1); + size_t const base = ((2 | (dist_slot & 1)) << footer_bits); + unsigned const dist_reduced = (unsigned)(dist - base); + if (dist_slot < kEndPosModelIndex) { + RC_encodeBitTreeReverse(&enc->rc, enc->states.dist_encoders + base - dist_slot - 1, footer_bits, dist_reduced); + } + else { + RC_encodeDirect(&enc->rc, dist_reduced >> kNumAlignBits, footer_bits - kNumAlignBits); + RC_encodeBitTreeReverse(&enc->rc, enc->states.dist_align_encoders, kNumAlignBits, dist_reduced & kAlignMask); + } + } + enc->states.reps[3] = enc->states.reps[2]; + enc->states.reps[2] = enc->states.reps[1]; + enc->states.reps[1] = enc->states.reps[0]; + enc->states.reps[0] = dist; + + ++enc->match_price_count; +} + +FORCE_INLINE_TEMPLATE +size_t LZMA_encodeChunkFast(LZMA2_ECtx *const enc, + FL2_dataBlock const block, + FL2_matchTable* const tbl, + int const struct_tbl, + size_t pos, + size_t const uncompressed_end) +{ + size_t const pos_mask = enc->pos_mask; + size_t prev = pos; + unsigned const search_depth = tbl->params.depth; + + while (pos < uncompressed_end && enc->rc.out_index < enc->chunk_size) { + size_t max_len; + const BYTE* data; + /* Table of distance restrictions for short matches */ + static const U32 max_dist_table[] = { 0, 0, 0, 1 << 6, 1 << 14 }; + /* Get a match from the table, extended to its full length */ + RMF_match best_match = RMF_getMatch(block, tbl, search_depth, struct_tbl, pos); + if (best_match.length < kMatchLenMin) { + ++pos; + continue; + } + /* Use if near enough */ + if (best_match.length >= 5 || best_match.dist < max_dist_table[best_match.length]) + best_match.dist += kNumReps; + else + best_match.length = 0; + + max_len = MIN(kMatchLenMax, block.end - pos); + data = block.data + pos; + + RMF_match best_rep = { 0, 0 }; + RMF_match rep_match; + /* Search all of the rep distances */ + for (rep_match.dist = 0; rep_match.dist < kNumReps; ++rep_match.dist) { + const BYTE *data_2 = data - enc->states.reps[rep_match.dist] - 1; + if (MEM_read16(data) != MEM_read16(data_2)) + continue; + + rep_match.length = (U32)(ZSTD_count(data + 2, data_2 + 2, data + max_len) + 2); + if (rep_match.length >= max_len) { + best_match = rep_match; + goto _encode; + } + if (rep_match.length > best_rep.length) + best_rep = rep_match; + } + /* Encode if it is kMatchLenMax or completes the block */ + if (best_match.length >= max_len) + goto _encode; + + if (best_rep.length >= 2) { + if (best_rep.length > best_match.length) { + best_match = best_rep; + } + else { + /* Modified ZSTD scheme for estimating cost */ + int const gain2 = (int)(best_rep.length * 3 - best_rep.dist); + int const gain1 = (int)(best_match.length * 3 - ZSTD_highbit32(best_match.dist + 1) + 1); + if (gain2 > gain1) { + DEBUGLOG(7, "Replace match (%u, %u) with rep (%u, %u)", best_match.length, best_match.dist, best_rep.length, best_rep.dist); + best_match = best_rep; + } + } + } + + if (best_match.length < kMatchLenMin) { + ++pos; + continue; + } + + for (size_t next = pos + 1; best_match.length < kMatchLenMax && next < uncompressed_end; ++next) { + /* lazy matching scheme from ZSTD */ + RMF_match next_match = RMF_getNextMatch(block, tbl, search_depth, struct_tbl, next); + if (next_match.length >= kMatchLenMin) { + best_rep.length = 0; + data = block.data + next; + max_len = MIN(kMatchLenMax, block.end - next); + for (rep_match.dist = 0; rep_match.dist < kNumReps; ++rep_match.dist) { + const BYTE *data_2 = data - enc->states.reps[rep_match.dist] - 1; + if (MEM_read16(data) != MEM_read16(data_2)) + continue; + + rep_match.length = (U32)(ZSTD_count(data + 2, data_2 + 2, data + max_len) + 2); + if (rep_match.length > best_rep.length) + best_rep = rep_match; + } + if (best_rep.length >= 3) { + int const gain2 = (int)(best_rep.length * 3 - best_rep.dist); + int const gain1 = (int)(best_match.length * 3 - ZSTD_highbit32((U32)best_match.dist + 1) + 1); + if (gain2 > gain1) { + DEBUGLOG(7, "Replace match (%u, %u) with rep (%u, %u)", best_match.length, best_match.dist, best_rep.length, best_rep.dist); + best_match = best_rep; + pos = next; + } + } + if (next_match.length >= 3 && next_match.dist != best_match.dist) { + int const gain2 = (int)(next_match.length * 4 - ZSTD_highbit32((U32)next_match.dist + 1)); /* raw approx */ + int const gain1 = (int)(best_match.length * 4 - ZSTD_highbit32((U32)best_match.dist + 1) + 4); + if (gain2 > gain1) { + DEBUGLOG(7, "Replace match (%u, %u) with match (%u, %u)", best_match.length, best_match.dist, next_match.length, next_match.dist + kNumReps); + best_match = next_match; + best_match.dist += kNumReps; + pos = next; + continue; + } + } + } + ++next; + /* Recheck next < uncompressed_end. uncompressed_end could be block.end so decrementing the max chunk size won't obviate the need. */ + if (next >= uncompressed_end) + break; + + next_match = RMF_getNextMatch(block, tbl, search_depth, struct_tbl, next); + if (next_match.length < 4) + break; + + data = block.data + next; + max_len = MIN(kMatchLenMax, block.end - next); + best_rep.length = 0; + + for (rep_match.dist = 0; rep_match.dist < kNumReps; ++rep_match.dist) { + const BYTE *data_2 = data - enc->states.reps[rep_match.dist] - 1; + if (MEM_read16(data) != MEM_read16(data_2)) + continue; + + rep_match.length = (U32)(ZSTD_count(data + 2, data_2 + 2, data + max_len) + 2); + if (rep_match.length > best_rep.length) + best_rep = rep_match; + } + if (best_rep.length >= 4) { + int const gain2 = (int)(best_rep.length * 4 - (best_rep.dist >> 1)); + int const gain1 = (int)(best_match.length * 4 - ZSTD_highbit32((U32)best_match.dist + 1) + 1); + if (gain2 > gain1) { + DEBUGLOG(7, "Replace match (%u, %u) with rep (%u, %u)", best_match.length, best_match.dist, best_rep.length, best_rep.dist); + best_match = best_rep; + pos = next; + } + } + if (next_match.dist != best_match.dist) { + int const gain2 = (int)(next_match.length * 4 - ZSTD_highbit32((U32)next_match.dist + 1)); + int const gain1 = (int)(best_match.length * 4 - ZSTD_highbit32((U32)best_match.dist + 1) + 7); + if (gain2 > gain1) { + DEBUGLOG(7, "Replace match (%u, %u) with match (%u, %u)", best_match.length, best_match.dist, next_match.length, next_match.dist + kNumReps); + best_match = next_match; + best_match.dist += kNumReps; + pos = next; + continue; + } + } + + break; + } +_encode: + assert(pos + best_match.length <= block.end); + + while (prev < pos) { + if (enc->rc.out_index >= enc->chunk_limit) + return prev; + + if (block.data[prev] != block.data[prev - enc->states.reps[0] - 1]) { + LZMA_encodeLiteralBuf(enc, block.data, prev); + ++prev; + } + else { + LZMA_encodeRepMatchShort(enc, prev & pos_mask); + ++prev; + } + } + + if(best_match.length >= kMatchLenMin) { + if (best_match.dist >= kNumReps) { + LZMA_encodeNormalMatch(enc, best_match.length, best_match.dist - kNumReps, pos & pos_mask); + pos += best_match.length; + prev = pos; + } + else { + LZMA_encodeRepMatchLong(enc, best_match.length, best_match.dist, pos & pos_mask); + pos += best_match.length; + prev = pos; + } + } + } + while (prev < pos && enc->rc.out_index < enc->chunk_limit) { + if (block.data[prev] != block.data[prev - enc->states.reps[0] - 1]) + LZMA_encodeLiteralBuf(enc, block.data, prev); + else + LZMA_encodeRepMatchShort(enc, prev & pos_mask); + ++prev; + } + return prev; +} + +/* + * Reverse the direction of the linked list generated by the optimal parser + */ +FORCE_NOINLINE +static void LZMA_reverseOptimalChain(LZMA2_node* const opt_buf, size_t cur) +{ + unsigned len = (unsigned)opt_buf[cur].len; + U32 dist = opt_buf[cur].dist; + + for(;;) { + unsigned const extra = (unsigned)opt_buf[cur].extra; + cur -= len; + + if (extra) { + opt_buf[cur].len = (U32)len; + len = extra; + if (extra == 1) { + opt_buf[cur].dist = dist; + dist = kNullDist; + --cur; + } + else { + opt_buf[cur].dist = 0; + --cur; + --len; + opt_buf[cur].dist = kNullDist; + opt_buf[cur].len = 1; + cur -= len; + } + } + + unsigned const next_len = opt_buf[cur].len; + U32 const next_dist = opt_buf[cur].dist; + + opt_buf[cur].dist = dist; + opt_buf[cur].len = (U32)len; + + if (cur == 0) + break; + + len = next_len; + dist = next_dist; + } +} + +static unsigned LZMA_getLiteralPrice(LZMA2_ECtx *const enc, size_t const pos, size_t const state, unsigned const prev_symbol, U32 symbol, unsigned const match_byte) +{ + const LZMA2_prob* const prob_table = LITERAL_PROBS(enc, pos, prev_symbol); + if (IS_LIT_STATE(state)) { + unsigned price = 0; + symbol |= 0x100; + do { + price += GET_PRICE(prob_table[symbol >> 8], (symbol >> 7) & 1); + symbol <<= 1; + } while (symbol < 0x10000); + return price; + } + return LZMA_getLiteralPriceMatched(prob_table, symbol, match_byte); +} + +/* + * Reset the hash object for encoding a new slice of a block + */ +static void LZMA_hashReset(LZMA2_ECtx *const enc, unsigned const dictionary_bits_3) +{ + enc->hash_dict_3 = (ptrdiff_t)1 << dictionary_bits_3; + enc->chain_mask_3 = enc->hash_dict_3 - 1; + memset(enc->hash_buf->table_3, 0xFF, sizeof(enc->hash_buf->table_3)); +} + +/* + * Create hash table and chain with dict size dictionary_bits_3. Frees any existing object. + */ +static int LZMA_hashCreate(LZMA2_ECtx *const enc, unsigned const dictionary_bits_3) +{ + DEBUGLOG(3, "Create hash chain : dict bits %u", dictionary_bits_3); + + if (enc->hash_buf) + free(enc->hash_buf); + + enc->hash_alloc_3 = (ptrdiff_t)1 << dictionary_bits_3; + enc->hash_buf = malloc(sizeof(LZMA2_hc3) + (enc->hash_alloc_3 - 1) * sizeof(S32)); + + if (enc->hash_buf == NULL) + return 1; + + LZMA_hashReset(enc, dictionary_bits_3); + + return 0; +} + +/* Create a hash chain for hybrid mode if options require one. + * Used for allocating before compression begins. Any existing table will be reused if + * it is at least as large as required. + */ +int LZMA2_hashAlloc(LZMA2_ECtx *const enc, const FL2_lzma2Parameters* const options) +{ + if (enc->strategy == FL2_ultra && enc->hash_alloc_3 < ((ptrdiff_t)1 << options->second_dict_bits)) + return LZMA_hashCreate(enc, options->second_dict_bits); + + return 0; +} + +#define GET_HASH_3(data) ((((MEM_readLE32(data)) << 8) * 506832829U) >> (32 - kHash3Bits)) + +/* Find matches nearer than the match from the RMF. If none is at least as long as + * the RMF match (most likely), insert that match at the end of the list. + */ +HINT_INLINE +size_t LZMA_hashGetMatches(LZMA2_ECtx *const enc, FL2_dataBlock const block, + ptrdiff_t const pos, + size_t const length_limit, + RMF_match const match) +{ + ptrdiff_t const hash_dict_3 = enc->hash_dict_3; + const BYTE* data = block.data; + LZMA2_hc3* const tbl = enc->hash_buf; + ptrdiff_t const chain_mask_3 = enc->chain_mask_3; + + enc->match_count = 0; + enc->hash_prev_index = MAX(enc->hash_prev_index, pos - hash_dict_3); + /* Update hash tables and chains for any positions that were skipped */ + while (++enc->hash_prev_index < pos) { + size_t hash = GET_HASH_3(data + enc->hash_prev_index); + tbl->hash_chain_3[enc->hash_prev_index & chain_mask_3] = tbl->table_3[hash]; + tbl->table_3[hash] = (S32)enc->hash_prev_index; + } + data += pos; + + size_t const hash = GET_HASH_3(data); + ptrdiff_t const first_3 = tbl->table_3[hash]; + tbl->table_3[hash] = (S32)pos; + + size_t max_len = 2; + + if (first_3 >= 0) { + int cycles = enc->match_cycles; + ptrdiff_t const end_index = pos - (((ptrdiff_t)match.dist < hash_dict_3) ? (ptrdiff_t)match.dist : hash_dict_3); + ptrdiff_t match_3 = first_3; + if (match_3 >= end_index) { + do { + --cycles; + const BYTE* data_2 = block.data + match_3; + size_t len_test = ZSTD_count(data + 1, data_2 + 1, data + length_limit) + 1; + if (len_test > max_len) { + enc->matches[enc->match_count].length = (U32)len_test; + enc->matches[enc->match_count].dist = (U32)(pos - match_3 - 1); + ++enc->match_count; + max_len = len_test; + if (len_test >= length_limit) + break; + } + if (cycles <= 0) + break; + match_3 = tbl->hash_chain_3[match_3 & chain_mask_3]; + } while (match_3 >= end_index); + } + } + tbl->hash_chain_3[pos & chain_mask_3] = (S32)first_3; + if ((unsigned)max_len < match.length) { + /* Insert the match from the RMF */ + enc->matches[enc->match_count] = match; + ++enc->match_count; + return match.length; + } + return max_len; +} + +/* The speed of this function is critical. The sections have many variables +* in common, so breaking it up into shorter functions is not feasible. +* For each position cur, starting at 1, check some or all possible +* encoding choices - a literal, 1-byte rep 0 match, all rep match lengths, and +* all match lengths at available distances. It also checks the combined +* sequences literal+rep0, rep+lit+rep0 and match+lit+rep0. +* If is_hybrid != 0, this method works in hybrid mode, using the +* hash chain to find shorter matches at near distances. */ +FORCE_INLINE_TEMPLATE +size_t LZMA_optimalParse(LZMA2_ECtx* const enc, FL2_dataBlock const block, + RMF_match match, + size_t const pos, + size_t const cur, + size_t len_end, + int const is_hybrid, + U32* const reps) +{ + LZMA2_node* const cur_opt = &enc->opt_buf[cur]; + size_t const pos_mask = enc->pos_mask; + size_t const pos_state = (pos & pos_mask); + const BYTE* const data = block.data + pos; + size_t const fast_length = enc->fast_length; + size_t prev_index = cur - cur_opt->len; + size_t state; + size_t bytes_avail; + U32 match_price; + U32 rep_match_price; + + /* Update the states according to how this location was reached */ + if (cur_opt->len == 1) { + /* Literal or 1-byte rep */ + const BYTE *next_state = (cur_opt->dist == 0) ? kShortRepNextStates : kLiteralNextStates; + state = next_state[enc->opt_buf[prev_index].state]; + } + else { + /* Match or rep match */ + size_t const dist = cur_opt->dist; + + if (cur_opt->extra) { + prev_index -= cur_opt->extra; + state = kState_RepAfterLit - ((dist >= kNumReps) & (cur_opt->extra == 1)); + } + else { + state = enc->opt_buf[prev_index].state; + state = MATCH_NEXT_STATE(state) + (dist < kNumReps); + } + const LZMA2_node *const prev_opt = &enc->opt_buf[prev_index]; + if (dist < kNumReps) { + /* Move the chosen rep to the front. + * The table is hideous but faster than branching :D */ + reps[0] = prev_opt->reps[dist]; + size_t table = 1 | (2 << 2) | (3 << 4) + | (0 << 8) | (2 << 10) | (3 << 12) + | (0L << 16) | (1L << 18) | (3L << 20) + | (0L << 24) | (1L << 26) | (2L << 28); + table >>= (dist << 3); + reps[1] = prev_opt->reps[table & 3]; + table >>= 2; + reps[2] = prev_opt->reps[table & 3]; + table >>= 2; + reps[3] = prev_opt->reps[table & 3]; + } + else { + reps[0] = (U32)(dist - kNumReps); + reps[1] = prev_opt->reps[0]; + reps[2] = prev_opt->reps[1]; + reps[3] = prev_opt->reps[2]; + } + } + cur_opt->state = state; + memcpy(cur_opt->reps, reps, sizeof(cur_opt->reps)); + LZMA2_prob const is_rep_prob = enc->states.is_rep[state]; + + { LZMA2_node *const next_opt = &enc->opt_buf[cur + 1]; + U32 const cur_price = cur_opt->price; + U32 const next_price = next_opt->price; + LZMA2_prob const is_match_prob = enc->states.is_match[state][pos_state]; + unsigned const cur_byte = *data; + unsigned const match_byte = *(data - reps[0] - 1); + + U32 cur_and_lit_price = cur_price + GET_PRICE_0(is_match_prob); + /* This is a compromise to try to filter out cases where literal + rep0 is unlikely to be cheaper */ + BYTE try_lit = cur_and_lit_price + kMinLitPrice / 2U <= next_price; + if (try_lit) { + /* cur_and_lit_price is used later for the literal + rep0 test */ + cur_and_lit_price += LZMA_getLiteralPrice(enc, pos, state, data[-1], cur_byte, match_byte); + /* Try literal */ + if (cur_and_lit_price < next_price) { + next_opt->price = cur_and_lit_price; + next_opt->len = 1; + MARK_LITERAL(*next_opt); + if (is_hybrid) /* Evaluates as a constant expression due to inlining */ + try_lit = 0; + } + } + match_price = cur_price + GET_PRICE_1(is_match_prob); + rep_match_price = match_price + GET_PRICE_1(is_rep_prob); + if (match_byte == cur_byte) { + /* Try 1-byte rep0 */ + U32 short_rep_price = rep_match_price + LZMA_getRepLen1Price(enc, state, pos_state); + if (short_rep_price <= next_opt->price) { + next_opt->price = short_rep_price; + next_opt->len = 1; + MARK_SHORT_REP(*next_opt); + } + } + bytes_avail = MIN(block.end - pos, kOptimizerBufferSize - 1 - cur); + if (bytes_avail < 2) + return len_end; + + /* If match_byte == cur_byte a rep0 begins at the current position */ + if (is_hybrid && try_lit && match_byte != cur_byte) { + /* Try literal + rep0 */ + const BYTE *const data_2 = data - reps[0]; + size_t limit = MIN(bytes_avail - 1, fast_length); + size_t len_test_2 = ZSTD_count(data + 1, data_2, data + 1 + limit); + if (len_test_2 >= 2) { + size_t const state_2 = LIT_NEXT_STATE(state); + size_t const pos_state_next = (pos + 1) & pos_mask; + U32 const next_rep_match_price = cur_and_lit_price + + GET_PRICE_1(enc->states.is_match[state_2][pos_state_next]) + + GET_PRICE_1(enc->states.is_rep[state_2]); + U32 const cur_and_len_price = next_rep_match_price + LZMA_getRepMatch0Price(enc, len_test_2, state_2, pos_state_next); + size_t const offset = cur + 1 + len_test_2; + if (cur_and_len_price < enc->opt_buf[offset].price) { + len_end = MAX(len_end, offset); + enc->opt_buf[offset].price = cur_and_len_price; + enc->opt_buf[offset].len = (unsigned)len_test_2; + enc->opt_buf[offset].dist = 0; + enc->opt_buf[offset].extra = 1; + } + } + } + } + + size_t const max_length = MIN(bytes_avail, fast_length); + size_t start_len = 2; + + if (match.length > 0) { + size_t len_test; + size_t len; + U32 cur_rep_price; + for (size_t rep_index = 0; rep_index < kNumReps; ++rep_index) { + const BYTE *const data_2 = data - reps[rep_index] - 1; + if (MEM_read16(data) != MEM_read16(data_2)) + continue; + /* Test is limited to fast_length, but it is rare for the RMF to miss the longest match, + * therefore this function is rarely called when a rep len > fast_length exists */ + len_test = ZSTD_count(data + 2, data_2 + 2, data + max_length) + 2; + len_end = MAX(len_end, cur + len_test); + cur_rep_price = rep_match_price + LZMA_getRepPrice(enc, rep_index, state, pos_state); + len = 2; + /* Try rep match */ + do { + U32 const cur_and_len_price = cur_rep_price + enc->states.rep_len_states.prices[pos_state][len - kMatchLenMin]; + LZMA2_node *const opt = &enc->opt_buf[cur + len]; + if (cur_and_len_price < opt->price) { + opt->price = cur_and_len_price; + opt->len = (unsigned)len; + opt->dist = (U32)rep_index; + opt->extra = 0; + } + } while (++len <= len_test); + + if (rep_index == 0) { + /* Save time by exluding normal matches not longer than the rep */ + start_len = len_test + 1; + } + /* rep + literal + rep0 is not common so this test is skipped for faster, non-hybrid encoding */ + if (is_hybrid && len_test + 3 <= bytes_avail && MEM_read16(data + len_test + 1) == MEM_read16(data_2 + len_test + 1)) { + /* Try rep + literal + rep0. + * The second rep may be > fast_length, but it is not worth the extra time to handle this case + * and the price table is not filled for it */ + size_t const len_test_2 = ZSTD_count(data + len_test + 3, + data_2 + len_test + 3, + data + MIN(len_test + 1 + fast_length, bytes_avail)) + 2; + size_t state_2 = REP_NEXT_STATE(state); + size_t pos_state_next = (pos + len_test) & pos_mask; + U32 rep_lit_rep_total_price = + cur_rep_price + enc->states.rep_len_states.prices[pos_state][len_test - kMatchLenMin] + + GET_PRICE_0(enc->states.is_match[state_2][pos_state_next]) + + LZMA_getLiteralPriceMatched(LITERAL_PROBS(enc, pos + len_test, data[len_test - 1]), + data[len_test], data_2[len_test]); + + state_2 = kState_LitAfterRep; + pos_state_next = (pos + len_test + 1) & pos_mask; + rep_lit_rep_total_price += + GET_PRICE_1(enc->states.is_match[state_2][pos_state_next]) + + GET_PRICE_1(enc->states.is_rep[state_2]); + size_t const offset = cur + len_test + 1 + len_test_2; + rep_lit_rep_total_price += LZMA_getRepMatch0Price(enc, len_test_2, state_2, pos_state_next); + if (rep_lit_rep_total_price < enc->opt_buf[offset].price) { + len_end = MAX(len_end, offset); + enc->opt_buf[offset].price = rep_lit_rep_total_price; + enc->opt_buf[offset].len = (unsigned)len_test_2; + enc->opt_buf[offset].dist = (U32)rep_index; + enc->opt_buf[offset].extra = (unsigned)(len_test + 1); + } + } + } + } + if (match.length >= start_len && max_length >= start_len) { + /* Try normal match */ + U32 const normal_match_price = match_price + GET_PRICE_0(is_rep_prob); + if (!is_hybrid) { + /* Normal mode - single match */ + size_t const length = MIN(match.length, max_length); + size_t const cur_dist = match.dist; + size_t const dist_slot = LZMA_getDistSlot(match.dist); + size_t len_test = length; + len_end = MAX(len_end, cur + length); + for (; len_test >= start_len; --len_test) { + U32 cur_and_len_price = normal_match_price + enc->states.len_states.prices[pos_state][len_test - kMatchLenMin]; + size_t const len_to_dist_state = LEN_TO_DIST_STATE(len_test); + + if (cur_dist < kNumFullDistances) + cur_and_len_price += enc->distance_prices[len_to_dist_state][cur_dist]; + else + cur_and_len_price += enc->dist_slot_prices[len_to_dist_state][dist_slot] + enc->align_prices[cur_dist & kAlignMask]; + + LZMA2_node *const opt = &enc->opt_buf[cur + len_test]; + if (cur_and_len_price < opt->price) { + opt->price = cur_and_len_price; + opt->len = (unsigned)len_test; + opt->dist = (U32)(cur_dist + kNumReps); + opt->extra = 0; + } + else break; + } + } + else { + /* Hybrid mode */ + size_t main_len; + + match.length = MIN(match.length, (U32)max_length); + /* Need to test max_length < 4 because the hash fn reads a U32 */ + if (match.length < 3 || max_length < 4) { + enc->matches[0] = match; + enc->match_count = 1; + main_len = match.length; + } + else { + main_len = LZMA_hashGetMatches(enc, block, pos, max_length, match); + } + ptrdiff_t match_index = enc->match_count - 1; + len_end = MAX(len_end, cur + main_len); + + /* Start with a match longer than the best rep if one exists */ + ptrdiff_t start_match = 0; + while (start_len > enc->matches[start_match].length) + ++start_match; + + enc->matches[start_match - 1].length = (U32)start_len - 1; /* Avoids an if..else branch in the loop. [-1] is ok */ + + for (; match_index >= start_match; --match_index) { + size_t len_test = enc->matches[match_index].length; + size_t const cur_dist = enc->matches[match_index].dist; + const BYTE *const data_2 = data - cur_dist - 1; + size_t const rep_0_pos = len_test + 1; + size_t dist_slot = LZMA_getDistSlot((U32)cur_dist); + U32 cur_and_len_price; + /* Test from the full length down to 1 more than the next shorter match */ + size_t base_len = enc->matches[match_index - 1].length + 1; + for (; len_test >= base_len; --len_test) { + cur_and_len_price = normal_match_price + enc->states.len_states.prices[pos_state][len_test - kMatchLenMin]; + size_t const len_to_dist_state = LEN_TO_DIST_STATE(len_test); + if (cur_dist < kNumFullDistances) + cur_and_len_price += enc->distance_prices[len_to_dist_state][cur_dist]; + else + cur_and_len_price += enc->dist_slot_prices[len_to_dist_state][dist_slot] + enc->align_prices[cur_dist & kAlignMask]; + + BYTE const sub_len = len_test < enc->matches[match_index].length; + + LZMA2_node *const opt = &enc->opt_buf[cur + len_test]; + if (cur_and_len_price < opt->price) { + opt->price = cur_and_len_price; + opt->len = (unsigned)len_test; + opt->dist = (U32)(cur_dist + kNumReps); + opt->extra = 0; + } + else if(sub_len) + break; /* End the tests if prices for shorter lengths are not lower than those already recorded */ + + if (!sub_len && rep_0_pos + 2 <= bytes_avail && MEM_read16(data + rep_0_pos) == MEM_read16(data_2 + rep_0_pos)) { + /* Try match + literal + rep0 */ + size_t const limit = MIN(rep_0_pos + fast_length, bytes_avail); + size_t const len_test_2 = ZSTD_count(data + rep_0_pos + 2, data_2 + rep_0_pos + 2, data + limit) + 2; + size_t state_2 = MATCH_NEXT_STATE(state); + size_t pos_state_next = (pos + len_test) & pos_mask; + U32 match_lit_rep_total_price = cur_and_len_price + + GET_PRICE_0(enc->states.is_match[state_2][pos_state_next]) + + LZMA_getLiteralPriceMatched(LITERAL_PROBS(enc, pos + len_test, data[len_test - 1]), + data[len_test], data_2[len_test]); + + state_2 = kState_LitAfterMatch; + pos_state_next = (pos_state_next + 1) & pos_mask; + match_lit_rep_total_price += + GET_PRICE_1(enc->states.is_match[state_2][pos_state_next]) + + GET_PRICE_1(enc->states.is_rep[state_2]); + size_t const offset = cur + rep_0_pos + len_test_2; + match_lit_rep_total_price += LZMA_getRepMatch0Price(enc, len_test_2, state_2, pos_state_next); + if (match_lit_rep_total_price < enc->opt_buf[offset].price) { + len_end = MAX(len_end, offset); + enc->opt_buf[offset].price = match_lit_rep_total_price; + enc->opt_buf[offset].len = (unsigned)len_test_2; + enc->opt_buf[offset].extra = (unsigned)rep_0_pos; + enc->opt_buf[offset].dist = (U32)(cur_dist + kNumReps); + } + } + } + } + } + } + return len_end; +} + +FORCE_NOINLINE +static void LZMA_initMatchesPos0(LZMA2_ECtx *const enc, + RMF_match const match, + size_t const pos_state, + size_t len, + unsigned const normal_match_price) +{ + if ((unsigned)len <= match.length) { + size_t const distance = match.dist; + size_t const slot = LZMA_getDistSlot(match.dist); + /* Test every available length of the match */ + do { + unsigned cur_and_len_price = normal_match_price + enc->states.len_states.prices[pos_state][len - kMatchLenMin]; + size_t const len_to_dist_state = LEN_TO_DIST_STATE(len); + + if (distance < kNumFullDistances) + cur_and_len_price += enc->distance_prices[len_to_dist_state][distance]; + else + cur_and_len_price += enc->align_prices[distance & kAlignMask] + enc->dist_slot_prices[len_to_dist_state][slot]; + + if (cur_and_len_price < enc->opt_buf[len].price) { + enc->opt_buf[len].price = cur_and_len_price; + enc->opt_buf[len].len = (unsigned)len; + enc->opt_buf[len].dist = (U32)(distance + kNumReps); + enc->opt_buf[len].extra = 0; + } + ++len; + } while ((U32)len <= match.length); + } +} + +FORCE_NOINLINE +static size_t LZMA_initMatchesPos0Best(LZMA2_ECtx *const enc, FL2_dataBlock const block, + RMF_match const match, + size_t const pos, + size_t start_len, + unsigned const normal_match_price) +{ + if (start_len <= match.length) { + size_t main_len; + if (match.length < 3 || block.end - pos < 4) { + enc->matches[0] = match; + enc->match_count = 1; + main_len = match.length; + } + else { + main_len = LZMA_hashGetMatches(enc, block, pos, MIN(block.end - pos, enc->fast_length), match); + } + + ptrdiff_t start_match = 0; + while (start_len > enc->matches[start_match].length) + ++start_match; + + enc->matches[start_match - 1].length = (U32)start_len - 1; /* Avoids an if..else branch in the loop. [-1] is ok */ + + size_t pos_state = pos & enc->pos_mask; + + for (ptrdiff_t match_index = enc->match_count - 1; match_index >= start_match; --match_index) { + size_t len_test = enc->matches[match_index].length; + size_t const distance = enc->matches[match_index].dist; + size_t const slot = LZMA_getDistSlot((U32)distance); + size_t const base_len = enc->matches[match_index - 1].length + 1; + /* Test every available match length at the shortest distance. The buffer is sorted */ + /* in order of increasing length, and therefore increasing distance too. */ + for (; len_test >= base_len; --len_test) { + unsigned cur_and_len_price = normal_match_price + + enc->states.len_states.prices[pos_state][len_test - kMatchLenMin]; + size_t const len_to_dist_state = LEN_TO_DIST_STATE(len_test); + + if (distance < kNumFullDistances) + cur_and_len_price += enc->distance_prices[len_to_dist_state][distance]; + else + cur_and_len_price += enc->align_prices[distance & kAlignMask] + enc->dist_slot_prices[len_to_dist_state][slot]; + + if (cur_and_len_price < enc->opt_buf[len_test].price) { + enc->opt_buf[len_test].price = cur_and_len_price; + enc->opt_buf[len_test].len = (unsigned)len_test; + enc->opt_buf[len_test].dist = (U32)(distance + kNumReps); + enc->opt_buf[len_test].extra = 0; + } + else break; + } + } + return main_len; + } + return 0; +} + +/* Test all available options at position 0 of the optimizer buffer. +* The prices at this point are all initialized to kInfinityPrice. +* This function must not be called at a position where no match is +* available. */ +FORCE_INLINE_TEMPLATE +size_t LZMA_initOptimizerPos0(LZMA2_ECtx *const enc, FL2_dataBlock const block, + RMF_match const match, + size_t const pos, + int const is_hybrid, + U32* const reps) +{ + size_t const max_length = MIN(block.end - pos, kMatchLenMax); + const BYTE *const data = block.data + pos; + const BYTE *data_2; + size_t rep_max_index = 0; + size_t rep_lens[kNumReps]; + + /* Find any rep matches */ + for (size_t i = 0; i < kNumReps; ++i) { + reps[i] = enc->states.reps[i]; + data_2 = data - reps[i] - 1; + if (MEM_read16(data) != MEM_read16(data_2)) { + rep_lens[i] = 0; + continue; + } + rep_lens[i] = ZSTD_count(data + 2, data_2 + 2, data + max_length) + 2; + if (rep_lens[i] > rep_lens[rep_max_index]) + rep_max_index = i; + } + if (rep_lens[rep_max_index] >= enc->fast_length) { + enc->opt_buf[0].len = (unsigned)(rep_lens[rep_max_index]); + enc->opt_buf[0].dist = (U32)rep_max_index; + return 0; + } + if (match.length >= enc->fast_length) { + enc->opt_buf[0].len = match.length; + enc->opt_buf[0].dist = match.dist + kNumReps; + return 0; + } + + unsigned const cur_byte = *data; + unsigned const match_byte = *(data - reps[0] - 1); + size_t const state = enc->states.state; + size_t const pos_state = pos & enc->pos_mask; + LZMA2_prob const is_match_prob = enc->states.is_match[state][pos_state]; + LZMA2_prob const is_rep_prob = enc->states.is_rep[state]; + + enc->opt_buf[0].state = state; + /* Set the price for literal */ + enc->opt_buf[1].price = GET_PRICE_0(is_match_prob) + + LZMA_getLiteralPrice(enc, pos, state, data[-1], cur_byte, match_byte); + MARK_LITERAL(enc->opt_buf[1]); + + unsigned const match_price = GET_PRICE_1(is_match_prob); + unsigned const rep_match_price = match_price + GET_PRICE_1(is_rep_prob); + if (match_byte == cur_byte) { + /* Try 1-byte rep0 */ + unsigned const short_rep_price = rep_match_price + LZMA_getRepLen1Price(enc, state, pos_state); + if (short_rep_price < enc->opt_buf[1].price) { + enc->opt_buf[1].price = short_rep_price; + MARK_SHORT_REP(enc->opt_buf[1]); + } + } + memcpy(enc->opt_buf[0].reps, reps, sizeof(enc->opt_buf[0].reps)); + enc->opt_buf[1].len = 1; + /* Test the rep match prices */ + for (size_t i = 0; i < kNumReps; ++i) { + size_t rep_len = rep_lens[i]; + if (rep_len < 2) + continue; + + unsigned const price = rep_match_price + LZMA_getRepPrice(enc, i, state, pos_state); + /* Test every available length of the rep */ + do { + unsigned const cur_and_len_price = price + enc->states.rep_len_states.prices[pos_state][rep_len - kMatchLenMin]; + if (cur_and_len_price < enc->opt_buf[rep_len].price) { + enc->opt_buf[rep_len].price = cur_and_len_price; + enc->opt_buf[rep_len].len = (unsigned)rep_len; + enc->opt_buf[rep_len].dist = (U32)i; + enc->opt_buf[rep_len].extra = 0; + } + } while (--rep_len >= kMatchLenMin); + } + unsigned const normal_match_price = match_price + GET_PRICE_0(is_rep_prob); + size_t const len = (rep_lens[0] >= 2) ? rep_lens[0] + 1 : 2; + /* Test the match prices */ + if (!is_hybrid) { + /* Normal mode */ + LZMA_initMatchesPos0(enc, match, pos_state, len, normal_match_price); + return MAX(match.length, rep_lens[rep_max_index]); + } + else { + /* Hybrid mode */ + size_t main_len = LZMA_initMatchesPos0Best(enc, block, match, pos, len, normal_match_price); + return MAX(main_len, rep_lens[rep_max_index]); + } +} + +FORCE_INLINE_TEMPLATE +size_t LZMA_encodeOptimumSequence(LZMA2_ECtx *const enc, FL2_dataBlock const block, + FL2_matchTable* const tbl, + int const struct_tbl, + int const is_hybrid, + size_t start_index, + size_t const uncompressed_end, + RMF_match match) +{ + size_t len_end = enc->len_end_max; + unsigned const search_depth = tbl->params.depth; + do { + size_t const pos_mask = enc->pos_mask; + + /* Reset all prices that were set last time */ + for (; (len_end & 3) != 0; --len_end) + enc->opt_buf[len_end].price = kInfinityPrice; + for (; len_end >= 4; len_end -= 4) { + enc->opt_buf[len_end].price = kInfinityPrice; + enc->opt_buf[len_end - 1].price = kInfinityPrice; + enc->opt_buf[len_end - 2].price = kInfinityPrice; + enc->opt_buf[len_end - 3].price = kInfinityPrice; + } + + /* Set everything up at position 0 */ + size_t pos = start_index; + U32 reps[kNumReps]; + len_end = LZMA_initOptimizerPos0(enc, block, match, pos, is_hybrid, reps); + match.length = 0; + size_t cur = 1; + + /* len_end == 0 if a match of fast_length was found */ + if (len_end > 0) { + ++pos; + for (; cur < len_end; ++cur, ++pos) { + /* Terminate if the farthest calculated price is too near the buffer end */ + if (len_end >= kOptimizerBufferSize - kOptimizerEndSize) { + U32 price = enc->opt_buf[cur].price; + /* This is a compromise to favor more distant end points + * even if the price is a bit higher */ + U32 const delta = price / (U32)cur / 2U; + for (size_t j = cur + 1; j <= len_end; j++) { + U32 const price2 = enc->opt_buf[j].price; + if (price >= price2) { + price = price2; + cur = j; + } + price += delta; + } + break; + } + + /* Skip ahead if a lower or equal price is available at greater distance */ + size_t const end = MIN(cur + kOptimizerSkipSize, len_end); + U32 price = enc->opt_buf[cur].price; + for (size_t j = cur + 1; j <= end; j++) { + U32 const price2 = enc->opt_buf[j].price; + if (price >= price2) { + price = price2; + pos += j - cur; + cur = j; + if (cur == len_end) + goto reverse; + } + } + + match = RMF_getMatch(block, tbl, search_depth, struct_tbl, pos); + if (match.length >= enc->fast_length) + break; + + len_end = LZMA_optimalParse(enc, block, match, pos, cur, len_end, is_hybrid, reps); + } +reverse: + DEBUGLOG(6, "End optimal parse at %u", (U32)cur); + LZMA_reverseOptimalChain(enc->opt_buf, cur); + } + /* Encode the selections in the buffer */ + size_t i = 0; + do { + unsigned const len = enc->opt_buf[i].len; + + if (len == 1 && enc->opt_buf[i].dist == kNullDist) { + LZMA_encodeLiteralBuf(enc, block.data, start_index + i); + ++i; + } + else { + size_t const pos_state = (start_index + i) & pos_mask; + U32 const dist = enc->opt_buf[i].dist; + /* Updating i separately for each case may allow a branch to be eliminated */ + if (dist >= kNumReps) { + LZMA_encodeNormalMatch(enc, len, dist - kNumReps, pos_state); + i += len; + } + else if(len == 1) { + LZMA_encodeRepMatchShort(enc, pos_state); + ++i; + } + else { + LZMA_encodeRepMatchLong(enc, len, dist, pos_state); + i += len; + } + } + } while (i < cur); + start_index += i; + /* Do another round if there is a long match pending, + * because the reps must be checked and the match encoded. */ + } while (match.length >= enc->fast_length && start_index < uncompressed_end && enc->rc.out_index < enc->chunk_size); + + enc->len_end_max = len_end; + + return start_index; +} + +static void FORCE_NOINLINE LZMA_fillAlignPrices(LZMA2_ECtx *const enc) +{ + unsigned i; + const LZMA2_prob *const probs = enc->states.dist_align_encoders; + for (i = 0; i < kAlignTableSize / 2; i++) { + U32 price = 0; + unsigned sym = i; + unsigned m = 1; + unsigned bit; + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[m], bit); m = (m << 1) + bit; + U32 const prob = probs[m]; + enc->align_prices[i] = price + GET_PRICE_0(prob); + enc->align_prices[i + 8] = price + GET_PRICE_1(prob); + } +} + +static void FORCE_NOINLINE LZMA_fillDistancesPrices(LZMA2_ECtx *const enc) +{ + U32 * const temp_prices = enc->distance_prices[kNumLenToPosStates - 1]; + + enc->match_price_count = 0; + + for (size_t i = kStartPosModelIndex / 2; i < kNumFullDistances / 2; i++) { + unsigned const dist_slot = distance_table[i]; + unsigned footer_bits = (dist_slot >> 1) - 1; + size_t base = ((2 | (dist_slot & 1)) << footer_bits); + const LZMA2_prob *probs = enc->states.dist_encoders + base * 2U; + base += i; + probs = probs - distance_table[base] - 1; + U32 price = 0; + unsigned m = 1; + unsigned sym = (unsigned)i; + unsigned const offset = (unsigned)1 << footer_bits; + + for (; footer_bits != 0; --footer_bits) { + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICE(probs[m], bit); + m = (m << 1) + bit; + }; + + unsigned const prob = probs[m]; + temp_prices[base] = price + GET_PRICE_0(prob); + temp_prices[base + offset] = price + GET_PRICE_1(prob); + } + + for (unsigned lps = 0; lps < kNumLenToPosStates; lps++) { + size_t slot; + size_t const dist_table_size2 = (enc->dist_price_table_size + 1) >> 1; + U32 *const dist_slot_prices = enc->dist_slot_prices[lps]; + const LZMA2_prob *const probs = enc->states.dist_slot_encoders[lps]; + + for (slot = 0; slot < dist_table_size2; slot++) { + /* dist_slot_prices[slot] = RcTree_GetPrice(encoder, kNumPosSlotBits, slot, p->ProbPrices); */ + U32 price; + unsigned bit; + unsigned sym = (unsigned)slot + (1 << (kNumPosSlotBits - 1)); + bit = sym & 1; sym >>= 1; price = GET_PRICE(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICE(probs[sym], bit); + unsigned const prob = probs[slot + (1 << (kNumPosSlotBits - 1))]; + dist_slot_prices[slot * 2] = price + GET_PRICE_0(prob); + dist_slot_prices[slot * 2 + 1] = price + GET_PRICE_1(prob); + } + + { + U32 delta = ((U32)((kEndPosModelIndex / 2 - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + for (slot = kEndPosModelIndex / 2; slot < dist_table_size2; slot++) { + dist_slot_prices[slot * 2] += delta; + dist_slot_prices[slot * 2 + 1] += delta; + delta += ((U32)1 << kNumBitPriceShiftBits); + } + } + + { + U32 *const dp = enc->distance_prices[lps]; + + dp[0] = dist_slot_prices[0]; + dp[1] = dist_slot_prices[1]; + dp[2] = dist_slot_prices[2]; + dp[3] = dist_slot_prices[3]; + + for (size_t i = 4; i < kNumFullDistances; i += 2) { + U32 slot_price = dist_slot_prices[distance_table[i]]; + dp[i] = slot_price + temp_prices[i]; + dp[i + 1] = slot_price + temp_prices[i + 1]; + } + } + } +} + +FORCE_INLINE_TEMPLATE +size_t LZMA_encodeChunkBest(LZMA2_ECtx *const enc, + FL2_dataBlock const block, + FL2_matchTable* const tbl, + int const struct_tbl, + size_t pos, + size_t const uncompressed_end) +{ + unsigned const search_depth = tbl->params.depth; + LZMA_fillDistancesPrices(enc); + LZMA_fillAlignPrices(enc); + LZMA_lengthStates_updatePrices(enc, &enc->states.len_states); + LZMA_lengthStates_updatePrices(enc, &enc->states.rep_len_states); + + while (pos < uncompressed_end && enc->rc.out_index < enc->chunk_size) + { + RMF_match const match = RMF_getMatch(block, tbl, search_depth, struct_tbl, pos); + if (match.length > 1) { + /* Template-like inline function */ + if (enc->strategy == FL2_ultra) { + pos = LZMA_encodeOptimumSequence(enc, block, tbl, struct_tbl, 1, pos, uncompressed_end, match); + } + else { + pos = LZMA_encodeOptimumSequence(enc, block, tbl, struct_tbl, 0, pos, uncompressed_end, match); + } + if (enc->match_price_count >= kMatchRepriceFrequency) { + LZMA_fillAlignPrices(enc); + LZMA_fillDistancesPrices(enc); + LZMA_lengthStates_updatePrices(enc, &enc->states.len_states); + } + if (enc->rep_len_price_count >= kRepLenRepriceFrequency) { + enc->rep_len_price_count = 0; + LZMA_lengthStates_updatePrices(enc, &enc->states.rep_len_states); + } + } + else { + if (block.data[pos] != block.data[pos - enc->states.reps[0] - 1]) { + LZMA_encodeLiteralBuf(enc, block.data, pos); + ++pos; + } + else { + LZMA_encodeRepMatchShort(enc, pos & enc->pos_mask); + ++pos; + } + } + } + return pos; +} + +static void LZMA_lengthStates_Reset(LZMA2_lenStates* const ls, unsigned const fast_length) +{ + ls->choice = kProbInitValue; + + for (size_t i = 0; i < (kNumPositionStatesMax << (kLenNumLowBits + 1)); ++i) + ls->low[i] = kProbInitValue; + + for (size_t i = 0; i < kLenNumHighSymbols; ++i) + ls->high[i] = kProbInitValue; + + ls->table_size = fast_length + 1 - kMatchLenMin; +} + +static void LZMA_encoderStates_Reset(LZMA2_encStates* const es, unsigned const lc, unsigned const lp, unsigned fast_length) +{ + es->state = 0; + + for (size_t i = 0; i < kNumReps; ++i) + es->reps[i] = 0; + + for (size_t i = 0; i < kNumStates; ++i) { + for (size_t j = 0; j < kNumPositionStatesMax; ++j) { + es->is_match[i][j] = kProbInitValue; + es->is_rep0_long[i][j] = kProbInitValue; + } + es->is_rep[i] = kProbInitValue; + es->is_rep_G0[i] = kProbInitValue; + es->is_rep_G1[i] = kProbInitValue; + es->is_rep_G2[i] = kProbInitValue; + } + size_t const num = (size_t)(kNumLiterals * kNumLitTables) << (lp + lc); + for (size_t i = 0; i < num; ++i) + es->literal_probs[i] = kProbInitValue; + + for (size_t i = 0; i < kNumLenToPosStates; ++i) { + LZMA2_prob *probs = es->dist_slot_encoders[i]; + for (size_t j = 0; j < (1 << kNumPosSlotBits); ++j) + probs[j] = kProbInitValue; + } + for (size_t i = 0; i < kNumFullDistances - kEndPosModelIndex; ++i) + es->dist_encoders[i] = kProbInitValue; + + LZMA_lengthStates_Reset(&es->len_states, fast_length); + LZMA_lengthStates_Reset(&es->rep_len_states, fast_length); + + for (size_t i = 0; i < (1 << kNumAlignBits); ++i) + es->dist_align_encoders[i] = kProbInitValue; +} + +BYTE LZMA2_getDictSizeProp(size_t const dictionary_size) +{ + BYTE dict_size_prop = 0; + for (BYTE bit = 11; bit < 32; ++bit) { + if (((size_t)2 << bit) >= dictionary_size) { + dict_size_prop = (bit - 11) << 1; + break; + } + if (((size_t)3 << bit) >= dictionary_size) { + dict_size_prop = ((bit - 11) << 1) | 1; + break; + } + } + return dict_size_prop; +} + +size_t LZMA2_compressBound(size_t src_size) +{ + /* Minimum average uncompressed size. An average size of half kChunkSize should be assumed + * to account for thread_count incomplete end chunks per block. LZMA expansion is < 2% so 1/16 + * is a safe overestimate. */ + static const unsigned chunk_min_avg = (kChunkSize - (kChunkSize / 16U)) / 2U; + /* Maximum size of data stored in a sequence of uncompressed chunks */ + return src_size + ((src_size + chunk_min_avg - 1) / chunk_min_avg) * 3 + 6; +} + +size_t LZMA2_encMemoryUsage(unsigned const chain_log, FL2_strategy const strategy, unsigned const thread_count) +{ + size_t size = sizeof(LZMA2_ECtx); + if(strategy == FL2_ultra) + size += sizeof(LZMA2_hc3) + (sizeof(U32) << chain_log) - sizeof(U32); + return size * thread_count; +} + +static void LZMA2_reset(LZMA2_ECtx *const enc, size_t const max_distance) +{ + DEBUGLOG(5, "LZMA encoder reset : max_distance %u", (unsigned)max_distance); + RC_reset(&enc->rc); + LZMA_encoderStates_Reset(&enc->states, enc->lc, enc->lp, enc->fast_length); + enc->pos_mask = (1 << enc->pb) - 1; + enc->lit_pos_mask = (1 << enc->lp) - 1; + U32 i = 0; + for (; max_distance > (size_t)1 << i; ++i) { + } + enc->dist_price_table_size = i * 2; + enc->rep_len_price_count = 0; + enc->match_price_count = 0; +} + +static BYTE LZMA_getLcLpPbCode(LZMA2_ECtx *const enc) +{ + return (BYTE)((enc->pb * 5 + enc->lp) * 9 + enc->lc); +} + +/* Integer square root from https://stackoverflow.com/a/1101217 */ +static U32 LZMA2_isqrt(U32 op) +{ + U32 res = 0; + /* "one" starts at the highest power of four <= than the argument. */ + U32 one = (U32)1 << (ZSTD_highbit32(op) & ~1); + + while (one != 0) { + if (op >= res + one) { + op -= res + one; + res = res + 2U * one; + } + res >>= 1; + one >>= 2; + } + return res; +} + +static BYTE LZMA2_isChunkIncompressible(const FL2_matchTable* const tbl, + FL2_dataBlock const block, size_t const start, + unsigned const strategy) +{ + if (block.end - start >= kMinTestChunkSize) { + static const size_t max_dist_table[][5] = { + { 0, 0, 0, 1U << 6, 1U << 14 }, /* fast */ + { 0, 0, 1U << 6, 1U << 14, 1U << 22 }, /* opt */ + { 0, 0, 1U << 6, 1U << 14, 1U << 22 } }; /* ultra */ + static const size_t margin_divisor[3] = { 60U, 45U, 120U }; + static const U32 dev_table[3] = { 24, 24, 20}; + + size_t const end = MIN(start + kChunkSize, block.end); + size_t const chunk_size = end - start; + size_t count = 0; + size_t const margin = chunk_size / margin_divisor[strategy]; + size_t const terminator = start + margin; + + if (tbl->is_struct) { + size_t prev_dist = 0; + for (size_t pos = start; pos < end; ) { + U32 const link = GetMatchLink(tbl->table, pos); + if (link == RADIX_NULL_LINK) { + ++pos; + ++count; + prev_dist = 0; + } + else { + size_t const length = GetMatchLength(tbl->table, pos); + size_t const dist = pos - GetMatchLink(tbl->table, pos); + if (length > 4) { + /* Increase the cost if it's not the same match */ + count += dist != prev_dist; + } + else { + /* Increment the cost for a short match. The cost is the entire length if it's too far */ + count += (dist < max_dist_table[strategy][length]) ? 1 : length; + } + pos += length; + prev_dist = dist; + } + if (count + terminator <= pos) + return 0; + } + } + else { + size_t prev_dist = 0; + for (size_t pos = start; pos < end; ) { + U32 const link = tbl->table[pos]; + if (link == RADIX_NULL_LINK) { + ++pos; + ++count; + prev_dist = 0; + } + else { + size_t const length = link >> RADIX_LINK_BITS; + size_t const dist = pos - (link & RADIX_LINK_MASK); + if (length > 4) + count += dist != prev_dist; + else + count += (dist < max_dist_table[strategy][length]) ? 1 : length; + pos += length; + prev_dist = dist; + } + if (count + terminator <= pos) + return 0; + } + } + + U32 char_count[256]; + U32 char_total = 0; + /* Expected normal character count * 4 */ + U32 const avg = (U32)(chunk_size / 64U); + + memset(char_count, 0, sizeof(char_count)); + for (size_t pos = start; pos < end; ++pos) + char_count[block.data[pos]] += 4; + /* Sum the deviations */ + for (size_t i = 0; i < 256; ++i) { + S32 delta = char_count[i] - avg; + char_total += delta * delta; + } + U32 sqrt_chunk = (chunk_size == kChunkSize) ? kSqrtChunkSize : LZMA2_isqrt((U32)chunk_size); + /* Result base on character count std dev */ + return LZMA2_isqrt(char_total) / sqrt_chunk <= dev_table[strategy]; + } + return 0; +} + +static size_t LZMA2_encodeChunk(LZMA2_ECtx *const enc, + FL2_matchTable* const tbl, + FL2_dataBlock const block, + size_t const pos, size_t const uncompressed_end) +{ + /* Template-like inline functions */ + if (enc->strategy == FL2_fast) { + if (tbl->is_struct) { + return LZMA_encodeChunkFast(enc, block, tbl, 1, + pos, uncompressed_end); + } + else { + return LZMA_encodeChunkFast(enc, block, tbl, 0, + pos, uncompressed_end); + } + } + else { + if (tbl->is_struct) { + return LZMA_encodeChunkBest(enc, block, tbl, 1, + pos, uncompressed_end); + } + else { + return LZMA_encodeChunkBest(enc, block, tbl, 0, + pos, uncompressed_end); + } + } +} + +size_t LZMA2_encode(LZMA2_ECtx *const enc, + FL2_matchTable* const tbl, + FL2_dataBlock const block, + const FL2_lzma2Parameters* const options, + int stream_prop, + FL2_atomic *const progress_in, + FL2_atomic *const progress_out, + int *const canceled) +{ + size_t const start = block.start; + + /* Output starts in the temp buffer */ + BYTE* out_dest = enc->out_buf; + enc->chunk_size = kTempMinOutput; + enc->chunk_limit = kTempBufferSize - kMaxMatchEncodeSize * 2; + + /* Each encoder writes a properties byte because the upstream encoder(s) could */ + /* write only uncompressed chunks with no properties. */ + BYTE encode_properties = 1; + BYTE incompressible = 0; + + if (block.end <= block.start) + return 0; + + enc->lc = options->lc; + enc->lp = MIN(options->lp, kNumLiteralPosBitsMax); + + if (enc->lc + enc->lp > kLcLpMax) + enc->lc = kLcLpMax - enc->lp; + + enc->pb = MIN(options->pb, kNumPositionBitsMax); + enc->strategy = options->strategy; + enc->fast_length = MIN(options->fast_length, kMatchLenMax); + enc->match_cycles = MIN(options->match_cycles, kMatchesMax - 1); + + LZMA2_reset(enc, block.end); + + if (enc->strategy == FL2_ultra) { + /* Create a hash chain to put the encoder into hybrid mode */ + if (enc->hash_alloc_3 < ((ptrdiff_t)1 << options->second_dict_bits)) { + if(LZMA_hashCreate(enc, options->second_dict_bits) != 0) + return FL2_ERROR(memory_allocation); + } + else { + LZMA_hashReset(enc, options->second_dict_bits); + } + enc->hash_prev_index = (start >= (size_t)enc->hash_dict_3) ? (ptrdiff_t)(start - enc->hash_dict_3) : (ptrdiff_t)-1; + } + enc->len_end_max = kOptimizerBufferSize - 1; + + /* Limit the matches near the end of this slice to not exceed block.end */ + RMF_limitLengths(tbl, block.end); + + for (size_t pos = start; pos < block.end;) { + size_t header_size = (stream_prop >= 0) + (encode_properties ? kChunkHeaderSize + 1 : kChunkHeaderSize); + LZMA2_encStates saved_states; + size_t next_index; + + RC_reset(&enc->rc); + RC_setOutputBuffer(&enc->rc, out_dest + header_size); + + if (!incompressible) { + size_t cur = pos; + size_t const end = (enc->strategy == FL2_fast) ? MIN(block.end, pos + kMaxChunkUncompressedSize - kMatchLenMax + 1) + : MIN(block.end, pos + kMaxChunkUncompressedSize - kOptimizerBufferSize + 2); /* last byte of opt_buf unused */ + + /* Copy states in case chunk is incompressible */ + saved_states = enc->states; + + if (pos == 0) { + /* First byte of the dictionary */ + LZMA_encodeLiteral(enc, 0, block.data[0], 0); + ++cur; + } + if (pos == start) { + /* After kTempMinOutput bytes we can write data to the match table because the */ + /* compressed data will never catch up with the table position being read. */ + cur = LZMA2_encodeChunk(enc, tbl, block, cur, end); + + if (header_size + enc->rc.out_index > kTempBufferSize) + return FL2_ERROR(internal); + + /* Switch to the match table as output buffer */ + out_dest = RMF_getTableAsOutputBuffer(tbl, start); + memcpy(out_dest, enc->out_buf, header_size + enc->rc.out_index); + enc->rc.out_buffer = out_dest + header_size; + + /* Now encode up to the full chunk size */ + enc->chunk_size = kChunkSize; + enc->chunk_limit = kMaxChunkCompressedSize - kMaxMatchEncodeSize * 2; + } + next_index = LZMA2_encodeChunk(enc, tbl, block, cur, end); + RC_flush(&enc->rc); + } + else { + next_index = MIN(pos + kChunkSize, block.end); + } + size_t compressed_size = enc->rc.out_index; + size_t uncompressed_size = next_index - pos; + + if (compressed_size > kMaxChunkCompressedSize || uncompressed_size > kMaxChunkUncompressedSize) + return FL2_ERROR(internal); + + BYTE* header = out_dest; + + if (stream_prop >= 0) { + *header++ = (BYTE)stream_prop; + stream_prop = -1; + } + + header[1] = (BYTE)((uncompressed_size - 1) >> 8); + header[2] = (BYTE)(uncompressed_size - 1); + /* Output an uncompressed chunk if necessary */ + if (incompressible || uncompressed_size + 3 <= compressed_size + header_size) { + DEBUGLOG(6, "Storing chunk : was %u => %u", (unsigned)uncompressed_size, (unsigned)compressed_size); + + header[0] = (pos == 0) ? kChunkUncompressedDictReset : kChunkUncompressed; + + /* Copy uncompressed data into the output */ + memcpy(header + 3, block.data + pos, uncompressed_size); + + compressed_size = uncompressed_size; + header_size = 3 + (header - out_dest); + + /* Restore states if compression was attempted */ + if (!incompressible) + enc->states = saved_states; + } + else { + DEBUGLOG(6, "Compressed chunk : %u => %u", (unsigned)uncompressed_size, (unsigned)compressed_size); + + if (pos == 0) + header[0] = kChunkCompressedFlag | kChunkAllReset; + else if (encode_properties) + header[0] = kChunkCompressedFlag | kChunkStatePropertiesReset; + else + header[0] = kChunkCompressedFlag | kChunkNothingReset; + + header[0] |= (BYTE)((uncompressed_size - 1) >> 16); + header[3] = (BYTE)((compressed_size - 1) >> 8); + header[4] = (BYTE)(compressed_size - 1); + if (encode_properties) { + header[5] = LZMA_getLcLpPbCode(enc); + encode_properties = 0; + } + } + if (incompressible || uncompressed_size + 3 <= compressed_size + (compressed_size >> kRandomFilterMarginBits) + header_size) { + /* Test the next chunk for compressibility */ + incompressible = LZMA2_isChunkIncompressible(tbl, block, next_index, enc->strategy); + } + out_dest += compressed_size + header_size; + + /* Update progress concurrently with other encoder threads */ + FL2_atomic_add(*progress_in, (long)(next_index - pos)); + FL2_atomic_add(*progress_out, (long)(compressed_size + header_size)); + + pos = next_index; + + if (*canceled) + return FL2_ERROR(canceled); + } + return out_dest - RMF_getTableAsOutputBuffer(tbl, start); +} diff --git a/builtins/flzma2/lzma2_enc.h b/builtins/flzma2/lzma2_enc.h new file mode 100644 index 0000000000000..6e7dcbbb34da4 --- /dev/null +++ b/builtins/flzma2/lzma2_enc.h @@ -0,0 +1,65 @@ +/* lzma2_enc.h -- LZMA2 Encoder +Based on LzmaEnc.h and Lzma2Enc.h : Igor Pavlov +Modified for FL2 by Conor McCarthy +Public domain +*/ + +#ifndef RADYX_LZMA2_ENCODER_H +#define RADYX_LZMA2_ENCODER_H + +#include "mem.h" +#include "data_block.h" +#include "radix_mf.h" +#include "atomic.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define kFastDistBits 12U + +#define LZMA2_END_MARKER '\0' +#define ENC_MIN_BYTES_PER_THREAD 0x1C000 /* Enough for 8 threads, 1 Mb dict, 2/16 overlap */ + + +typedef struct LZMA2_ECtx_s LZMA2_ECtx; + +typedef struct +{ + unsigned lc; + unsigned lp; + unsigned pb; + unsigned fast_length; + unsigned match_cycles; + FL2_strategy strategy; + unsigned second_dict_bits; + unsigned reset_interval; +} FL2_lzma2Parameters; + + +LZMA2_ECtx* LZMA2_createECtx(void); + +void LZMA2_freeECtx(LZMA2_ECtx *const enc); + +int LZMA2_hashAlloc(LZMA2_ECtx *const enc, const FL2_lzma2Parameters* const options); + +size_t LZMA2_encode(LZMA2_ECtx *const enc, + FL2_matchTable* const tbl, + FL2_dataBlock const block, + const FL2_lzma2Parameters* const options, + int stream_prop, + FL2_atomic *const progress_in, + FL2_atomic *const progress_out, + int *const canceled); + +BYTE LZMA2_getDictSizeProp(size_t const dictionary_size); + +size_t LZMA2_compressBound(size_t src_size); + +size_t LZMA2_encMemoryUsage(unsigned const chain_log, FL2_strategy const strategy, unsigned const thread_count); + +#if defined (__cplusplus) +} +#endif + +#endif /* RADYX_LZMA2_ENCODER_H */ \ No newline at end of file diff --git a/builtins/flzma2/mem.h b/builtins/flzma2/mem.h new file mode 100644 index 0000000000000..5da248756ffd4 --- /dev/null +++ b/builtins/flzma2/mem.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef MEM_H_MODULE +#define MEM_H_MODULE + +#if defined (__cplusplus) +extern "C" { +#endif + +/*-**************************************** +* Dependencies +******************************************/ +#include /* size_t, ptrdiff_t */ +#include /* memcpy */ + + +/*-**************************************** +* Compiler specifics +******************************************/ +#if defined(_MSC_VER) /* Visual Studio */ +# include /* _byteswap_ulong */ +# include /* _byteswap_* */ +#endif +#if defined(__GNUC__) +# define MEM_STATIC static __inline __attribute__((unused)) +#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define MEM_STATIC static inline +#elif defined(_MSC_VER) +# define MEM_STATIC static __inline +#else +# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 /* compat. with non-clang compilers */ +#endif + +/* code only tested on 32 and 64 bits systems */ +#define MEM_STATIC_ASSERT(c) { enum { MEM_static_assert = 1/(int)(!!(c)) }; } +MEM_STATIC void MEM_check(void) { MEM_STATIC_ASSERT((sizeof(size_t)==4) || (sizeof(size_t)==8)); } + + +/*-************************************************************** +* Basic Types +*****************************************************************/ +#if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef int16_t S16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef int64_t S64; +#else +# include +#if CHAR_BIT != 8 +# error "this implementation requires char to be exactly 8-bit type" +#endif + typedef unsigned char BYTE; +#if USHRT_MAX != 65535 +# error "this implementation requires short to be exactly 16-bit type" +#endif + typedef unsigned short U16; + typedef signed short S16; +#if UINT_MAX != 4294967295 +# error "this implementation requires int to be exactly 32-bit type" +#endif + typedef unsigned int U32; + typedef signed int S32; +/* note : there are no limits defined for long long type in C90. + * limits exist in C99, however, in such case, is preferred */ + typedef unsigned long long U64; + typedef signed long long S64; +#endif + + +/*-************************************************************** +* Memory I/O +*****************************************************************/ +/* MEM_FORCE_MEMORY_ACCESS : + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (i.e., not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets depending on alignment. + * In some circumstances, it's the only known way to get the most performance (i.e. GCC + ARMv6) + * See http://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef MEM_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define MEM_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || defined(__GNUC__) +# define MEM_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +MEM_STATIC unsigned MEM_32bits(void) { return sizeof(size_t)==4; } +MEM_STATIC unsigned MEM_64bits(void) { return sizeof(size_t)==8; } + +MEM_STATIC unsigned MEM_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + +#if defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==2) + +/* violates C standard, by lying on structure alignment. +Only use if no other choice to achieve best performance on target platform */ +MEM_STATIC U16 MEM_read16(const void* memPtr) { return *(const U16*) memPtr; } +MEM_STATIC U32 MEM_read32(const void* memPtr) { return *(const U32*) memPtr; } +MEM_STATIC U64 MEM_read64(const void* memPtr) { return *(const U64*) memPtr; } +MEM_STATIC size_t MEM_readST(const void* memPtr) { return *(const size_t*) memPtr; } + +MEM_STATIC void MEM_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +MEM_STATIC void MEM_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } +MEM_STATIC void MEM_write64(void* memPtr, U64 value) { *(U64*)memPtr = value; } + +#elif defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +#if defined(_MSC_VER) || (defined(__INTEL_COMPILER) && defined(WIN32)) + __pragma( pack(push, 1) ) + typedef struct { U16 v; } unalign16; + typedef struct { U32 v; } unalign32; + typedef struct { U64 v; } unalign64; + typedef struct { size_t v; } unalignArch; + __pragma( pack(pop) ) +#else + typedef struct { U16 v; } __attribute__((packed)) unalign16; + typedef struct { U32 v; } __attribute__((packed)) unalign32; + typedef struct { U64 v; } __attribute__((packed)) unalign64; + typedef struct { size_t v; } __attribute__((packed)) unalignArch; +#endif + +MEM_STATIC U16 MEM_read16(const void* ptr) { return ((const unalign16*)ptr)->v; } +MEM_STATIC U32 MEM_read32(const void* ptr) { return ((const unalign32*)ptr)->v; } +MEM_STATIC U64 MEM_read64(const void* ptr) { return ((const unalign64*)ptr)->v; } +MEM_STATIC size_t MEM_readST(const void* ptr) { return ((const unalignArch*)ptr)->v; } + +MEM_STATIC void MEM_write16(void* memPtr, U16 value) { ((unalign16*)memPtr)->v = value; } +MEM_STATIC void MEM_write32(void* memPtr, U32 value) { ((unalign32*)memPtr)->v = value; } +MEM_STATIC void MEM_write64(void* memPtr, U64 value) { ((unalign64*)memPtr)->v = value; } + +#else + +/* default method, safe and standard. + can sometimes prove slower */ + +MEM_STATIC U16 MEM_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +MEM_STATIC U32 MEM_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +MEM_STATIC U64 MEM_read64(const void* memPtr) +{ + U64 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +MEM_STATIC size_t MEM_readST(const void* memPtr) +{ + size_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +MEM_STATIC void MEM_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +MEM_STATIC void MEM_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +MEM_STATIC void MEM_write64(void* memPtr, U64 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* MEM_FORCE_MEMORY_ACCESS */ + +MEM_STATIC U32 MEM_swap32(U32 in) +{ +#if defined(_MSC_VER) /* Visual Studio */ + return _byteswap_ulong(in); +#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ + || (defined(__clang__) && __has_builtin(__builtin_bswap32)) + return __builtin_bswap32(in); +#else + return ((in << 24) & 0xff000000 ) | + ((in << 8) & 0x00ff0000 ) | + ((in >> 8) & 0x0000ff00 ) | + ((in >> 24) & 0x000000ff ); +#endif +} + +MEM_STATIC U64 MEM_swap64(U64 in) +{ +#if defined(_MSC_VER) /* Visual Studio */ + return _byteswap_uint64(in); +#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ + || (defined(__clang__) && __has_builtin(__builtin_bswap64)) + return __builtin_bswap64(in); +#else + return ((in << 56) & 0xff00000000000000ULL) | + ((in << 40) & 0x00ff000000000000ULL) | + ((in << 24) & 0x0000ff0000000000ULL) | + ((in << 8) & 0x000000ff00000000ULL) | + ((in >> 8) & 0x00000000ff000000ULL) | + ((in >> 24) & 0x0000000000ff0000ULL) | + ((in >> 40) & 0x000000000000ff00ULL) | + ((in >> 56) & 0x00000000000000ffULL); +#endif +} + +MEM_STATIC size_t MEM_swapST(size_t in) +{ + if (MEM_32bits()) + return (size_t)MEM_swap32((U32)in); + else + return (size_t)MEM_swap64((U64)in); +} + +/*=== Little endian r/w ===*/ + +MEM_STATIC U16 MEM_readLE16(const void* memPtr) +{ + if (MEM_isLittleEndian()) + return MEM_read16(memPtr); + else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)(p[0] + (p[1]<<8)); + } +} + +MEM_STATIC void MEM_writeLE16(void* memPtr, U16 val) +{ + if (MEM_isLittleEndian()) { + MEM_write16(memPtr, val); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE)val; + p[1] = (BYTE)(val>>8); + } +} + +MEM_STATIC U32 MEM_readLE24(const void* memPtr) +{ + return MEM_readLE16(memPtr) + (((const BYTE*)memPtr)[2] << 16); +} + +MEM_STATIC void MEM_writeLE24(void* memPtr, U32 val) +{ + MEM_writeLE16(memPtr, (U16)val); + ((BYTE*)memPtr)[2] = (BYTE)(val>>16); +} + +MEM_STATIC U32 MEM_readLE32(const void* memPtr) +{ + if (MEM_isLittleEndian()) + return MEM_read32(memPtr); + else + return MEM_swap32(MEM_read32(memPtr)); +} + +MEM_STATIC void MEM_writeLE32(void* memPtr, U32 val32) +{ + if (MEM_isLittleEndian()) + MEM_write32(memPtr, val32); + else + MEM_write32(memPtr, MEM_swap32(val32)); +} + +MEM_STATIC U64 MEM_readLE64(const void* memPtr) +{ + if (MEM_isLittleEndian()) + return MEM_read64(memPtr); + else + return MEM_swap64(MEM_read64(memPtr)); +} + +MEM_STATIC void MEM_writeLE64(void* memPtr, U64 val64) +{ + if (MEM_isLittleEndian()) + MEM_write64(memPtr, val64); + else + MEM_write64(memPtr, MEM_swap64(val64)); +} + +MEM_STATIC size_t MEM_readLEST(const void* memPtr) +{ + if (MEM_32bits()) + return (size_t)MEM_readLE32(memPtr); + else + return (size_t)MEM_readLE64(memPtr); +} + +MEM_STATIC void MEM_writeLEST(void* memPtr, size_t val) +{ + if (MEM_32bits()) + MEM_writeLE32(memPtr, (U32)val); + else + MEM_writeLE64(memPtr, (U64)val); +} + +/*=== Big endian r/w ===*/ + +MEM_STATIC U32 MEM_readBE32(const void* memPtr) +{ + if (MEM_isLittleEndian()) + return MEM_swap32(MEM_read32(memPtr)); + else + return MEM_read32(memPtr); +} + +MEM_STATIC void MEM_writeBE32(void* memPtr, U32 val32) +{ + if (MEM_isLittleEndian()) + MEM_write32(memPtr, MEM_swap32(val32)); + else + MEM_write32(memPtr, val32); +} + +MEM_STATIC U64 MEM_readBE64(const void* memPtr) +{ + if (MEM_isLittleEndian()) + return MEM_swap64(MEM_read64(memPtr)); + else + return MEM_read64(memPtr); +} + +MEM_STATIC void MEM_writeBE64(void* memPtr, U64 val64) +{ + if (MEM_isLittleEndian()) + MEM_write64(memPtr, MEM_swap64(val64)); + else + MEM_write64(memPtr, val64); +} + +MEM_STATIC size_t MEM_readBEST(const void* memPtr) +{ + if (MEM_32bits()) + return (size_t)MEM_readBE32(memPtr); + else + return (size_t)MEM_readBE64(memPtr); +} + +MEM_STATIC void MEM_writeBEST(void* memPtr, size_t val) +{ + if (MEM_32bits()) + MEM_writeBE32(memPtr, (U32)val); + else + MEM_writeBE64(memPtr, (U64)val); +} + + +#if defined (__cplusplus) +} +#endif + +#endif /* MEM_H_MODULE */ diff --git a/builtins/flzma2/platform.h b/builtins/flzma2/platform.h new file mode 100644 index 0000000000000..155ebcd1eb9c8 --- /dev/null +++ b/builtins/flzma2/platform.h @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016-present, Przemyslaw Skibinski, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef PLATFORM_H_MODULE +#define PLATFORM_H_MODULE + +#if defined (__cplusplus) +extern "C" { +#endif + + + +/* ************************************** +* Compiler Options +****************************************/ +#if defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS /* Disable Visual Studio warning messages for fopen, strncpy, strerror */ +# if (_MSC_VER <= 1800) /* 1800 == Visual Studio 2013 */ +# define _CRT_SECURE_NO_DEPRECATE /* VS2005 - must be declared before and */ +# define snprintf sprintf_s /* snprintf unsupported by Visual <= 2013 */ +# endif +#endif + + +/* ************************************** +* Detect 64-bit OS +* http://nadeausoftware.com/articles/2012/02/c_c_tip_how_detect_processor_type_using_compiler_predefined_macros +****************************************/ +#if defined __ia64 || defined _M_IA64 /* Intel Itanium */ \ + || defined __powerpc64__ || defined __ppc64__ || defined __PPC64__ /* POWER 64-bit */ \ + || (defined __sparc && (defined __sparcv9 || defined __sparc_v9__ || defined __arch64__)) || defined __sparc64__ /* SPARC 64-bit */ \ + || defined __x86_64__s || defined _M_X64 /* x86 64-bit */ \ + || defined __arm64__ || defined __aarch64__ || defined __ARM64_ARCH_8__ /* ARM 64-bit */ \ + || (defined __mips && (__mips == 64 || __mips == 4 || __mips == 3)) /* MIPS 64-bit */ \ + || defined _LP64 || defined __LP64__ /* NetBSD, OpenBSD */ || defined __64BIT__ /* AIX */ || defined _ADDR64 /* Cray */ \ + || (defined __SIZEOF_POINTER__ && __SIZEOF_POINTER__ == 8) /* gcc */ +# if !defined(__64BIT__) +# define __64BIT__ 1 +# endif +#endif + + +/* ********************************************************* +* Turn on Large Files support (>4GB) for 32-bit Linux/Unix +***********************************************************/ +#if !defined(__64BIT__) || defined(__MINGW32__) /* No point defining Large file for 64 bit but MinGW-w64 requires it */ +# if !defined(_FILE_OFFSET_BITS) +# define _FILE_OFFSET_BITS 64 /* turn off_t into a 64-bit type for ftello, fseeko */ +# endif +# if !defined(_LARGEFILE_SOURCE) /* obsolete macro, replaced with _FILE_OFFSET_BITS */ +# define _LARGEFILE_SOURCE 1 /* Large File Support extension (LFS) - fseeko, ftello */ +# endif +# if defined(_AIX) || defined(__hpux) +# define _LARGE_FILES /* Large file support on 32-bits AIX and HP-UX */ +# endif +#endif + + +/* ************************************************************ +* Detect POSIX version +* PLATFORM_POSIX_VERSION = 0 for non-Unix e.g. Windows +* PLATFORM_POSIX_VERSION = 1 for Unix-like but non-POSIX +* PLATFORM_POSIX_VERSION > 1 is equal to found _POSIX_VERSION +* Value of PLATFORM_POSIX_VERSION can be forced on command line +***************************************************************/ +#ifndef PLATFORM_POSIX_VERSION + +# if (defined(__APPLE__) && defined(__MACH__)) || defined(__SVR4) || defined(_AIX) || defined(__hpux) /* POSIX.1-2001 (SUSv3) conformant */ \ + || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) /* BSD distros */ + /* exception rule : force posix version to 200112L, + * note: it's better to use unistd.h's _POSIX_VERSION whenever possible */ +# define PLATFORM_POSIX_VERSION 200112L + +/* try to determine posix version through official unistd.h's _POSIX_VERSION (http://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html). + * note : there is no simple way to know in advance if is present or not on target system, + * Posix specification mandates its presence and its content, but target system must respect this spec. + * It's necessary to _not_ #include whenever target OS is not unix-like + * otherwise it will block preprocessing stage. + * The following list of build macros tries to "guess" if target OS is likely unix-like, and therefore can #include + */ +# elif !defined(_WIN32) \ + && (defined(__unix__) || defined(__unix) \ + || defined(__midipix__) || defined(__VMS) || defined(__HAIKU__)) + +# if defined(__linux__) || defined(__linux) +# ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200112L /* feature test macro : https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html */ +# endif +# endif +# include /* declares _POSIX_VERSION */ +# if defined(_POSIX_VERSION) /* POSIX compliant */ +# define PLATFORM_POSIX_VERSION _POSIX_VERSION +# else +# define PLATFORM_POSIX_VERSION 1 +# endif + +# else /* non-unix target platform (like Windows) */ +# define PLATFORM_POSIX_VERSION 0 +# endif + +#endif /* PLATFORM_POSIX_VERSION */ + +/*-********************************************* +* Detect if isatty() and fileno() are available +************************************************/ +#if (defined(__linux__) && (PLATFORM_POSIX_VERSION > 1)) \ + || (PLATFORM_POSIX_VERSION >= 200112L) \ + || defined(__DJGPP__) \ + || defined(__MSYS__) +# include /* isatty */ +# define IS_CONSOLE(stdStream) isatty(fileno(stdStream)) +#elif defined(MSDOS) || defined(OS2) || defined(__CYGWIN__) +# include /* _isatty */ +# define IS_CONSOLE(stdStream) _isatty(_fileno(stdStream)) +#elif defined(WIN32) || defined(_WIN32) +# include /* _isatty */ +# include /* DeviceIoControl, HANDLE, FSCTL_SET_SPARSE */ +# include /* FILE */ +static __inline int IS_CONSOLE(FILE* stdStream) { + DWORD dummy; + return _isatty(_fileno(stdStream)) && GetConsoleMode((HANDLE)_get_osfhandle(_fileno(stdStream)), &dummy); +} +#else +# define IS_CONSOLE(stdStream) 0 +#endif + + +/****************************** +* OS-specific IO behaviors +******************************/ +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) +# include /* _O_BINARY */ +# include /* _setmode, _fileno, _get_osfhandle */ +# if !defined(__DJGPP__) +# include /* DeviceIoControl, HANDLE, FSCTL_SET_SPARSE */ +# include /* FSCTL_SET_SPARSE */ +# define SET_BINARY_MODE(file) { int const unused=_setmode(_fileno(file), _O_BINARY); (void)unused; } +# define SET_SPARSE_FILE_MODE(file) { DWORD dw; DeviceIoControl((HANDLE) _get_osfhandle(_fileno(file)), FSCTL_SET_SPARSE, 0, 0, 0, 0, &dw, 0); } +# else +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +# define SET_SPARSE_FILE_MODE(file) +# endif +#else +# define SET_BINARY_MODE(file) +# define SET_SPARSE_FILE_MODE(file) +#endif + + +#ifndef ZSTD_SPARSE_DEFAULT +# if (defined(__APPLE__) && defined(__MACH__)) +# define ZSTD_SPARSE_DEFAULT 0 +# else +# define ZSTD_SPARSE_DEFAULT 1 +# endif +#endif + + +#ifndef ZSTD_START_SYMBOLLIST_FRAME +# ifdef __linux__ +# define ZSTD_START_SYMBOLLIST_FRAME 2 +# elif defined __APPLE__ +# define ZSTD_START_SYMBOLLIST_FRAME 4 +# else +# define ZSTD_START_SYMBOLLIST_FRAME 0 +# endif +#endif + + +#ifndef ZSTD_SETPRIORITY_SUPPORT + /* mandates presence of and support for setpriority() : http://man7.org/linux/man-pages/man2/setpriority.2.html */ +# define ZSTD_SETPRIORITY_SUPPORT (PLATFORM_POSIX_VERSION >= 200112L) +#endif + + +#ifndef ZSTD_NANOSLEEP_SUPPORT + /* mandates support of nanosleep() within : http://man7.org/linux/man-pages/man2/nanosleep.2.html */ +# if (defined(__linux__) && (PLATFORM_POSIX_VERSION >= 199309L)) \ + || (PLATFORM_POSIX_VERSION >= 200112L) +# define ZSTD_NANOSLEEP_SUPPORT 1 +# else +# define ZSTD_NANOSLEEP_SUPPORT 0 +# endif +#endif + + +#if defined (__cplusplus) +} +#endif + +#endif /* PLATFORM_H_MODULE */ diff --git a/builtins/flzma2/radix_bitpack.c b/builtins/flzma2/radix_bitpack.c new file mode 100644 index 0000000000000..258dc1905f01d --- /dev/null +++ b/builtins/flzma2/radix_bitpack.c @@ -0,0 +1,58 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include "mem.h" /* U32, U64 */ +#include "fl2_threading.h" +#include "fl2_internal.h" +#include "radix_internal.h" + +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +#define RMF_BITPACK + +#define RADIX_MAX_LENGTH BITPACK_MAX_LENGTH + +#define InitMatchLink(pos, link) tbl->table[pos] = link + +#define GetMatchLink(link) (tbl->table[link] & RADIX_LINK_MASK) + +#define GetInitialMatchLink(pos) tbl->table[pos] + +#define GetMatchLength(pos) (tbl->table[pos] >> RADIX_LINK_BITS) + +#define SetMatchLink(pos, link, length) tbl->table[pos] = (link) | ((U32)(length) << RADIX_LINK_BITS) + +#define SetMatchLength(pos, link, length) tbl->table[pos] = (link) | ((U32)(length) << RADIX_LINK_BITS) + +#define SetMatchLinkAndLength(pos, link, length) tbl->table[pos] = (link) | ((U32)(length) << RADIX_LINK_BITS) + +#define SetNull(pos) tbl->table[pos] = RADIX_NULL_LINK + +#define IsNull(pos) (tbl->table[pos] == RADIX_NULL_LINK) + +BYTE* RMF_bitpackAsOutputBuffer(FL2_matchTable* const tbl, size_t const pos) +{ + return (BYTE*)(tbl->table + pos); +} + +/* Restrict the match lengths so that they don't reach beyond pos */ +void RMF_bitpackLimitLengths(FL2_matchTable* const tbl, size_t const pos) +{ + DEBUGLOG(5, "RMF_limitLengths : end %u, max length %u", (U32)pos, RADIX_MAX_LENGTH); + SetNull(pos - 1); + for (U32 length = 2; length < RADIX_MAX_LENGTH && length <= pos; ++length) { + U32 const link = tbl->table[pos - length]; + if (link != RADIX_NULL_LINK) + tbl->table[pos - length] = (MIN(length, link >> RADIX_LINK_BITS) << RADIX_LINK_BITS) | (link & RADIX_LINK_MASK); + } +} + +#include "radix_engine.h" \ No newline at end of file diff --git a/builtins/flzma2/radix_engine.h b/builtins/flzma2/radix_engine.h new file mode 100644 index 0000000000000..7bccaab308174 --- /dev/null +++ b/builtins/flzma2/radix_engine.h @@ -0,0 +1,1018 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include + +#define MAX_READ_BEYOND_DEPTH 2 + +/* If a repeating byte is found, fill that section of the table with matches of distance 1 */ +static size_t RMF_handleRepeat(RMF_builder* const tbl, const BYTE* const data_block, size_t const start, ptrdiff_t i, U32 depth) +{ + /* Normally the last 2 bytes, but may be 4 if depth == 4 */ + ptrdiff_t const last_2 = i + MAX_REPEAT / 2 - 1; + + /* Find the start */ + i += (4 - (i & 3)) & 3; + U32 u = *(U32*)(data_block + i); + while (i != 0 && *(U32*)(data_block + i - 4) == u) + i -= 4; + while (i != 0 && data_block[i - 1] == (BYTE)u) + --i; + + ptrdiff_t const rpt_index = i; + /* No point if it's in the overlap region */ + if (last_2 >= (ptrdiff_t)start) { + U32 len = depth; + /* Set matches at distance 1 and available length */ + for (i = last_2; i > rpt_index && len <= RADIX_MAX_LENGTH; --i) { + SetMatchLinkAndLength(i, (U32)(i - 1), len); + ++len; + } + /* Set matches at distance 1 and max length */ + for (; i > rpt_index; --i) + SetMatchLinkAndLength(i, (U32)(i - 1), RADIX_MAX_LENGTH); + } + return rpt_index; +} + +/* If a 2-byte repeat is found, fill that section of the table with matches of distance 2 */ +static size_t RMF_handleRepeat2(RMF_builder* const tbl, const BYTE* const data_block, size_t const start, ptrdiff_t i, U32 depth) +{ + /* Normally the last 2 bytes, but may be 4 if depth == 4 */ + ptrdiff_t const last_2 = i + MAX_REPEAT * 2U - 4; + + /* Find the start */ + ptrdiff_t realign = i & 1; + i += (4 - (i & 3)) & 3; + U32 u = *(U32*)(data_block + i); + while (i != 0 && *(U32*)(data_block + i - 4) == u) + i -= 4; + while (i != 0 && data_block[i - 1] == data_block[i + 1]) + --i; + i += (i & 1) ^ realign; + + ptrdiff_t const rpt_index = i; + /* No point if it's in the overlap region */ + if (i >= (ptrdiff_t)start) { + U32 len = depth + (data_block[last_2 + depth] == data_block[last_2]); + /* Set matches at distance 2 and available length */ + for (i = last_2; i > rpt_index && len <= RADIX_MAX_LENGTH; i -= 2) { + SetMatchLinkAndLength(i, (U32)(i - 2), len); + len += 2; + } + /* Set matches at distance 2 and max length */ + for (; i > rpt_index; i -= 2) + SetMatchLinkAndLength(i, (U32)(i - 2), RADIX_MAX_LENGTH); + } + return rpt_index; +} + +/* Initialization for the reference algortithm */ +#ifdef RMF_REFERENCE +static void RMF_initReference(FL2_matchTable* const tbl, const void* const data, size_t const end) +{ + const BYTE* const data_block = (const BYTE*)data; + ptrdiff_t const block_size = end - 1; + size_t st_index = 0; + for (ptrdiff_t i = 0; i < block_size; ++i) + { + size_t const radix_16 = ((size_t)data_block[i] << 8) | data_block[i + 1]; + U32 const prev = tbl->list_heads[radix_16].head; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(i, prev, 2U); + tbl->list_heads[radix_16].head = (U32)i; + ++tbl->list_heads[radix_16].count; + } + else { + SetNull(i); + tbl->list_heads[radix_16].head = (U32)i; + tbl->list_heads[radix_16].count = 1; + tbl->stack[st_index++] = (U32)radix_16; + } + } + SetNull(end - 1); + tbl->end_index = (U32)st_index; + tbl->st_index = ATOMIC_INITIAL_VALUE; +} +#endif + +void +#ifdef RMF_BITPACK +RMF_bitpackInit +#else +RMF_structuredInit +#endif +(FL2_matchTable* const tbl, const void* const data, size_t const end) +{ + if (end <= 2) { + for (size_t i = 0; i < end; ++i) + SetNull(i); + tbl->end_index = 0; + return; + } +#ifdef RMF_REFERENCE + if (tbl->params.use_ref_mf) { + RMF_initReference(tbl, data, end); + return; + } +#endif + + SetNull(0); + + const BYTE* const data_block = (const BYTE*)data; + size_t st_index = 0; + /* Initial 2-byte radix value */ + size_t radix_16 = ((size_t)data_block[0] << 8) | data_block[1]; + tbl->stack[st_index++] = (U32)radix_16; + tbl->list_heads[radix_16].head = 0; + tbl->list_heads[radix_16].count = 1; + + radix_16 = ((size_t)((BYTE)radix_16) << 8) | data_block[2]; + + ptrdiff_t i = 1; + ptrdiff_t const block_size = end - 2; + for (; i < block_size; ++i) { + /* Pre-load the next value for speed increase on some hardware. Execution can continue while memory read is pending */ + size_t const next_radix = ((size_t)((BYTE)radix_16) << 8) | data_block[i + 2]; + + U32 const prev = tbl->list_heads[radix_16].head; + if (prev != RADIX_NULL_LINK) { + /* Link this position to the previous occurrence */ + InitMatchLink(i, prev); + /* Set the previous to this position */ + tbl->list_heads[radix_16].head = (U32)i; + ++tbl->list_heads[radix_16].count; + radix_16 = next_radix; + } + else { + SetNull(i); + tbl->list_heads[radix_16].head = (U32)i; + tbl->list_heads[radix_16].count = 1; + tbl->stack[st_index++] = (U32)radix_16; + radix_16 = next_radix; + } + } + /* Handle the last value */ + if (tbl->list_heads[radix_16].head != RADIX_NULL_LINK) + SetMatchLinkAndLength(block_size, tbl->list_heads[radix_16].head, 2); + else + SetNull(block_size); + + /* Never a match at the last byte */ + SetNull(end - 1); + + tbl->end_index = (U32)st_index; +} + +/* Copy the list into a buffer and recurse it there. This decreases cache misses and allows */ +/* data characters to be loaded every fourth pass and stored for use in the next 4 passes */ +static void RMF_recurseListsBuffered(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + size_t link, + U32 depth, + U32 const max_depth, + U32 orig_list_count, + size_t const stack_base) +{ + if (orig_list_count < 2 || tbl->match_buffer_limit < 2) + return; + + /* Create an offset data buffer pointer for reading the next bytes */ + const BYTE* data_src = data_block + depth; + size_t start = 0; + + do { + U32 list_count = (U32)(start + orig_list_count); + + if (list_count > tbl->match_buffer_limit) + list_count = (U32)tbl->match_buffer_limit; + + size_t count = start; + size_t prev_link = (size_t)-1; + size_t rpt = 0; + size_t rpt_tail = link; + for (; count < list_count; ++count) { + /* Pre-load next link */ + size_t const next_link = GetMatchLink(link); + size_t dist = prev_link - link; + if (dist > 2) { + /* Get 4 data characters for later. This doesn't block on a cache miss. */ + tbl->match_buffer[count].src.u32 = MEM_read32(data_src + link); + /* Record the actual location of this suffix */ + tbl->match_buffer[count].from = (U32)link; + /* Initialize the next link */ + tbl->match_buffer[count].next = (U32)(count + 1) | (depth << 24); + rpt = 0; + prev_link = link; + rpt_tail = link; + link = next_link; + } + else { + rpt += 3 - dist; + /* Do the usual if the repeat is too short */ + if (rpt < MAX_REPEAT - 2) { + /* Get 4 data characters for later. This doesn't block on a cache miss. */ + tbl->match_buffer[count].src.u32 = MEM_read32(data_src + link); + /* Record the actual location of this suffix */ + tbl->match_buffer[count].from = (U32)link; + /* Initialize the next link */ + tbl->match_buffer[count].next = (U32)(count + 1) | (depth << 24); + prev_link = link; + link = next_link; + } + else { + /* Eliminate the repeat from the linked list to save time */ + if (dist == 1) { + link = RMF_handleRepeat(tbl, data_block, block_start, link, depth); + count -= MAX_REPEAT / 2; + orig_list_count -= (U32)(rpt_tail - link); + } + else { + link = RMF_handleRepeat2(tbl, data_block, block_start, link, depth); + count -= MAX_REPEAT - 1; + orig_list_count -= (U32)(rpt_tail - link) >> 1; + } + rpt = 0; + list_count = (U32)(start + orig_list_count); + + if (list_count > tbl->match_buffer_limit) + list_count = (U32)tbl->match_buffer_limit; + } + } + } + count = list_count; + /* Make the last element circular so pre-loading doesn't read past the end. */ + tbl->match_buffer[count - 1].next = (U32)(count - 1) | (depth << 24); + U32 overlap = 0; + if (list_count < (U32)(start + orig_list_count)) { + overlap = list_count >> MATCH_BUFFER_OVERLAP; + overlap += !overlap; + } + RMF_recurseListChunk(tbl, data_block, block_start, depth, max_depth, list_count, stack_base); + orig_list_count -= (U32)(list_count - start); + /* Copy everything back, except the last link which never changes, and any extra overlap */ + count -= overlap + (overlap == 0); +#ifdef RMF_BITPACK + if (max_depth > RADIX_MAX_LENGTH) for (size_t pos = 0; pos < count; ++pos) { + size_t const from = tbl->match_buffer[pos].from; + if (from < block_start) + return; + U32 length = tbl->match_buffer[pos].next >> 24; + length = (length > RADIX_MAX_LENGTH) ? RADIX_MAX_LENGTH : length; + size_t const next = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + SetMatchLinkAndLength(from, tbl->match_buffer[next].from, length); + } + else +#endif + for (size_t pos = 0; pos < count; ++pos) { + size_t const from = tbl->match_buffer[pos].from; + if (from < block_start) + return; + U32 const length = tbl->match_buffer[pos].next >> 24; + size_t const next = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + SetMatchLinkAndLength(from, tbl->match_buffer[next].from, length); + } + start = 0; + if (overlap) { + size_t dest = 0; + for (size_t src = list_count - overlap; src < list_count; ++src) { + tbl->match_buffer[dest].from = tbl->match_buffer[src].from; + tbl->match_buffer[dest].src.u32 = MEM_read32(data_src + tbl->match_buffer[src].from); + tbl->match_buffer[dest].next = (U32)(dest + 1) | (depth << 24); + ++dest; + } + start = dest; + } + } while (orig_list_count != 0); +} + +/* Parse the list with an upper bound check on data reads. Stop at the point where bound checks are not required. */ +/* Buffering is used so that parsing can continue below the bound to find a few matches without altering the main table. */ +static void RMF_recurseListsBound(RMF_builder* const tbl, + const BYTE* const data_block, + ptrdiff_t const block_size, + RMF_tableHead* const list_head, + U32 max_depth) +{ + U32 list_count = list_head->count; + if (list_count < 2) + return; + + ptrdiff_t link = list_head->head; + ptrdiff_t const bounded_size = max_depth + MAX_READ_BEYOND_DEPTH; + ptrdiff_t const bounded_start = block_size - MIN(block_size, bounded_size); + size_t count = 0; + size_t extra_count = (max_depth >> 4) + 4; + + list_count = MIN((U32)bounded_size, list_count); + list_count = MIN(list_count, (U32)tbl->match_buffer_size); + for (; count < list_count && extra_count; ++count) { + ptrdiff_t next_link = GetMatchLink(link); + if (link >= bounded_start) { + --list_head->count; + if (next_link < bounded_start) + list_head->head = (U32)next_link; + } + else { + --extra_count; + } + /* Record the actual location of this suffix */ + tbl->match_buffer[count].from = (U32)link; + /* Initialize the next link */ + tbl->match_buffer[count].next = (U32)(count + 1) | ((U32)2 << 24); + link = next_link; + } + list_count = (U32)count; + ptrdiff_t limit = block_size - 2; + /* Create an offset data buffer pointer for reading the next bytes */ + const BYTE* data_src = data_block + 2; + U32 depth = 3; + size_t pos = 0; + size_t st_index = 0; + RMF_listTail* const tails_8 = tbl->tails_8; + do { + link = tbl->match_buffer[pos].from; + if (link < limit) { + size_t const radix_8 = data_src[link]; + /* Seen this char before? */ + U32 const prev = tails_8[radix_8].prev_index; + tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tails_8[radix_8].list_count; + /* Link the previous occurrence to this one and record the new length */ + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tails_8[radix_8].list_count = 1; + /* Add the new sub list to the stack */ + tbl->stack[st_index].head = (U32)pos; + /* This will be converted to a count at the end */ + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + } + ++pos; + } while (pos < list_count); + /* Convert radix values on the stack to counts and reset any used tail slots */ + for (size_t j = 0; j < st_index; ++j) { + tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + tbl->stack[j].count = tails_8[tbl->stack[j].count].list_count; + } + while (st_index > 0) { + size_t prev_st_index; + + /* Pop an item off the stack */ + --st_index; + list_count = tbl->stack[st_index].count; + if (list_count < 2) /* Nothing to match with */ + continue; + + pos = tbl->stack[st_index].head; + depth = (tbl->match_buffer[pos].next >> 24); + if (depth >= max_depth) + continue; + link = tbl->match_buffer[pos].from; + if (link < bounded_start) { + /* Chain starts before the bounded region */ + continue; + } + data_src = data_block + depth; + limit = block_size - depth; + ++depth; + prev_st_index = st_index; + do { + link = tbl->match_buffer[pos].from; + if (link < limit) { + size_t const radix_8 = data_src[link]; + U32 const prev = tails_8[radix_8].prev_index; + tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)pos; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + } + pos = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + } while (--list_count != 0); + for (size_t j = prev_st_index; j < st_index; ++j) { + tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + tbl->stack[j].count = tails_8[tbl->stack[j].count].list_count; + } + } + /* Copy everything back above the bound */ + --count; + for (pos = 0; pos < count; ++pos) { + ptrdiff_t const from = tbl->match_buffer[pos].from; + if (from < bounded_start) + break; + + U32 length = tbl->match_buffer[pos].next >> 24; + length = MIN(length, (U32)(block_size - from)); + length = MIN(length, RADIX_MAX_LENGTH); + + size_t const next = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + SetMatchLinkAndLength(from, tbl->match_buffer[next].from, length); + } +} + +/* Compare each string with all others to find the best match */ +static void RMF_bruteForce(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + size_t link, + size_t const list_count, + U32 const depth, + U32 const max_depth) +{ + const BYTE* data_src = data_block + depth; + size_t buffer[MAX_BRUTE_FORCE_LIST_SIZE + 1]; + size_t const limit = max_depth - depth; + size_t i = 1; + + buffer[0] = link; + /* Pre-load all locations */ + do { + link = GetMatchLink(link); + buffer[i] = link; + } while (++i < list_count); + + i = 0; + do { + size_t longest = 0; + size_t j = i + 1; + size_t longest_index = j; + const BYTE* const data = data_src + buffer[i]; + do { + const BYTE* data_2 = data_src + buffer[j]; + size_t len_test = 0; + while (data[len_test] == data_2[len_test] && len_test < limit) + ++len_test; + + if (len_test > longest) { + longest_index = j; + longest = len_test; + if (len_test >= limit) + break; + } + } while (++j < list_count); + + if (longest > 0) + SetMatchLinkAndLength(buffer[i], (U32)buffer[longest_index], depth + (U32)longest); + + ++i; + /* Test with block_start to avoid wasting time matching strings in the overlap region with each other */ + } while (i < list_count - 1 && buffer[i] >= block_start); +} + +/* RMF_recurseLists16() : + * Match strings at depth 2 using a 16-bit radix to lengthen to depth 4 + */ +static void RMF_recurseLists16(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + size_t link, + U32 count, + U32 const max_depth) +{ + U32 const table_max_depth = MIN(max_depth, RADIX_MAX_LENGTH); + /* Offset data pointer. This function is only called at depth 2 */ + const BYTE* const data_src = data_block + 2; + /* Load radix values from the data chars */ + size_t next_radix_8 = data_src[link]; + size_t next_radix_16 = next_radix_8 + ((size_t)(data_src[link + 1]) << 8); + size_t reset_list[RADIX8_TABLE_SIZE]; + size_t reset_count = 0; + size_t st_index = 0; + /* Last one is done separately */ + --count; + do + { + /* Pre-load the next link */ + size_t const next_link = GetInitialMatchLink(link); + size_t const radix_8 = next_radix_8; + size_t const radix_16 = next_radix_16; + /* Initialization doesn't set lengths to 2 because it's a waste of time if buffering is used */ + SetMatchLength(link, (U32)next_link, 2); + + next_radix_8 = data_src[next_link]; + next_radix_16 = next_radix_8 + ((size_t)(data_src[next_link + 1]) << 8); + + U32 prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)link; + if (prev != RADIX_NULL_LINK) { + /* Link the previous occurrence to this one at length 3. */ + /* This will be overwritten if a 4 is found. */ + SetMatchLinkAndLength(prev, (U32)link, 3); + } + else { + reset_list[reset_count++] = radix_8; + } + + prev = tbl->tails_16[radix_16].prev_index; + tbl->tails_16[radix_16].prev_index = (U32)link; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[radix_16].list_count; + /* Link at length 4, overwriting the 3 */ + SetMatchLinkAndLength(prev, (U32)link, 4); + } + else { + tbl->tails_16[radix_16].list_count = 1; + tbl->stack[st_index].head = (U32)link; + /* Store a reference to this table location to retrieve the count at the end */ + tbl->stack[st_index].count = (U32)radix_16; + ++st_index; + } + link = next_link; + } while (--count > 0); + + /* Do the last location */ + U32 prev = tbl->tails_8[next_radix_8].prev_index; + if (prev != RADIX_NULL_LINK) + SetMatchLinkAndLength(prev, (U32)link, 3); + + prev = tbl->tails_16[next_radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[next_radix_16].list_count; + SetMatchLinkAndLength(prev, (U32)link, 4); + } + + for (size_t i = 0; i < reset_count; ++i) + tbl->tails_8[reset_list[i]].prev_index = RADIX_NULL_LINK; + + for (size_t i = 0; i < st_index; ++i) { + tbl->tails_16[tbl->stack[i].count].prev_index = RADIX_NULL_LINK; + tbl->stack[i].count = tbl->tails_16[tbl->stack[i].count].list_count; + } + + while (st_index > 0) { + --st_index; + U32 const list_count = tbl->stack[st_index].count; + if (list_count < 2) { + /* Nothing to do */ + continue; + } + link = tbl->stack[st_index].head; + if (link < block_start) + continue; + if (st_index > STACK_SIZE - RADIX16_TABLE_SIZE + && st_index > STACK_SIZE - list_count) + { + /* Potential stack overflow. Rare. */ + continue; + } + /* The current depth */ + U32 const depth = GetMatchLength(link); + if (list_count <= MAX_BRUTE_FORCE_LIST_SIZE) { + /* Quicker to use brute force, each string compared with all previous strings */ + RMF_bruteForce(tbl, data_block, + block_start, + link, + list_count, + depth, + table_max_depth); + continue; + } + /* Send to the buffer at depth 4 */ + RMF_recurseListsBuffered(tbl, + data_block, + block_start, + link, + (BYTE)depth, + (BYTE)max_depth, + list_count, + st_index); + } +} + +#if 0 +/* Unbuffered complete processing to max_depth. + * This may be faster on CPUs without a large memory cache. + */ +static void RMF_recurseListsUnbuf16(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + size_t link, + U32 count, + U32 const max_depth) +{ + /* Offset data pointer. This method is only called at depth 2 */ + const BYTE* data_src = data_block + 2; + /* Load radix values from the data chars */ + size_t next_radix_8 = data_src[link]; + size_t next_radix_16 = next_radix_8 + ((size_t)(data_src[link + 1]) << 8); + RMF_listTail* tails_8 = tbl->tails_8; + size_t reset_list[RADIX8_TABLE_SIZE]; + size_t reset_count = 0; + size_t st_index = 0; + /* Last one is done separately */ + --count; + do + { + /* Pre-load the next link */ + size_t next_link = GetInitialMatchLink(link); + /* Initialization doesn't set lengths to 2 because it's a waste of time if buffering is used */ + SetMatchLength(link, (U32)next_link, 2); + size_t radix_8 = next_radix_8; + size_t radix_16 = next_radix_16; + next_radix_8 = data_src[next_link]; + next_radix_16 = next_radix_8 + ((size_t)(data_src[next_link + 1]) << 8); + U32 prev = tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + /* Link the previous occurrence to this one at length 3. */ + /* This will be overwritten if a 4 is found. */ + SetMatchLinkAndLength(prev, (U32)link, 3); + } + else { + reset_list[reset_count++] = radix_8; + } + tails_8[radix_8].prev_index = (U32)link; + prev = tbl->tails_16[radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[radix_16].list_count; + /* Link at length 4, overwriting the 3 */ + SetMatchLinkAndLength(prev, (U32)link, 4); + } + else { + tbl->tails_16[radix_16].list_count = 1; + tbl->stack[st_index].head = (U32)link; + tbl->stack[st_index].count = (U32)radix_16; + ++st_index; + } + tbl->tails_16[radix_16].prev_index = (U32)link; + link = next_link; + } while (--count > 0); + /* Do the last location */ + U32 prev = tails_8[next_radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)link, 3); + } + prev = tbl->tails_16[next_radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[next_radix_16].list_count; + SetMatchLinkAndLength(prev, (U32)link, 4); + } + for (size_t i = 0; i < reset_count; ++i) { + tails_8[reset_list[i]].prev_index = RADIX_NULL_LINK; + } + reset_count = 0; + for (size_t i = 0; i < st_index; ++i) { + tbl->tails_16[tbl->stack[i].count].prev_index = RADIX_NULL_LINK; + tbl->stack[i].count = tbl->tails_16[tbl->stack[i].count].list_count; + } + while (st_index > 0) { + --st_index; + U32 list_count = tbl->stack[st_index].count; + if (list_count < 2) { + /* Nothing to do */ + continue; + } + link = tbl->stack[st_index].head; + if (link < block_start) + continue; + if (st_index > STACK_SIZE - RADIX16_TABLE_SIZE + && st_index > STACK_SIZE - list_count) + { + /* Potential stack overflow. Rare. */ + continue; + } + /* The current depth */ + U32 depth = GetMatchLength(link); + if (list_count <= MAX_BRUTE_FORCE_LIST_SIZE) { + /* Quicker to use brute force, each string compared with all previous strings */ + RMF_bruteForce(tbl, data_block, + block_start, + link, + list_count, + depth, + max_depth); + continue; + } + const BYTE* data_src = data_block + depth; + size_t next_radix_8 = data_src[link]; + size_t next_radix_16 = next_radix_8 + ((size_t)(data_src[link + 1]) << 8); + /* Next depth for 1 extra char */ + ++depth; + /* and for 2 */ + U32 depth_2 = depth + 1; + size_t prev_st_index = st_index; + /* Last location is done separately */ + --list_count; + /* Last pass is done separately. Both of these values are always even. */ + if (depth_2 < max_depth) { + do { + size_t radix_8 = next_radix_8; + size_t radix_16 = next_radix_16; + size_t next_link = GetMatchLink(link); + next_radix_8 = data_src[next_link]; + next_radix_16 = next_radix_8 + ((size_t)(data_src[next_link + 1]) << 8); + size_t prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + /* Odd numbered match length, will be overwritten if 2 chars are matched */ + SetMatchLinkAndLength(prev, (U32)(link), depth); + } + else { + reset_list[reset_count++] = radix_8; + } + tbl->tails_8[radix_8].prev_index = (U32)link; + prev = tbl->tails_16[radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[radix_16].list_count; + SetMatchLinkAndLength(prev, (U32)(link), depth_2); + } + else { + tbl->tails_16[radix_16].list_count = 1; + tbl->stack[st_index].head = (U32)(link); + tbl->stack[st_index].count = (U32)(radix_16); + ++st_index; + } + tbl->tails_16[radix_16].prev_index = (U32)(link); + link = next_link; + } while (--list_count != 0); + size_t prev = tbl->tails_8[next_radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)(link), depth); + } + prev = tbl->tails_16[next_radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_16[next_radix_16].list_count; + SetMatchLinkAndLength(prev, (U32)(link), depth_2); + } + for (size_t i = prev_st_index; i < st_index; ++i) { + tbl->tails_16[tbl->stack[i].count].prev_index = RADIX_NULL_LINK; + tbl->stack[i].count = tbl->tails_16[tbl->stack[i].count].list_count; + } + for (size_t i = 0; i < reset_count; ++i) { + tails_8[reset_list[i]].prev_index = RADIX_NULL_LINK; + } + reset_count = 0; + } + else { + do { + size_t radix_8 = next_radix_8; + size_t radix_16 = next_radix_16; + size_t next_link = GetMatchLink(link); + next_radix_8 = data_src[next_link]; + next_radix_16 = next_radix_8 + ((size_t)(data_src[next_link + 1]) << 8); + size_t prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)(link), depth); + } + else { + reset_list[reset_count++] = radix_8; + } + tbl->tails_8[radix_8].prev_index = (U32)link; + prev = tbl->tails_16[radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)(link), depth_2); + } + else { + tbl->stack[st_index].count = (U32)radix_16; + ++st_index; + } + tbl->tails_16[radix_16].prev_index = (U32)(link); + link = next_link; + } while (--list_count != 0); + size_t prev = tbl->tails_8[next_radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)(link), depth); + } + prev = tbl->tails_16[next_radix_16].prev_index; + if (prev != RADIX_NULL_LINK) { + SetMatchLinkAndLength(prev, (U32)(link), depth_2); + } + for (size_t i = prev_st_index; i < st_index; ++i) { + tbl->tails_16[tbl->stack[i].count].prev_index = RADIX_NULL_LINK; + } + st_index = prev_st_index; + for (size_t i = 0; i < reset_count; ++i) { + tails_8[reset_list[i]].prev_index = RADIX_NULL_LINK; + } + reset_count = 0; + } + } +} +#endif + +#ifdef RMF_REFERENCE + +/* Simple, slow, complete parsing for reference */ +static void RMF_recurseListsReference(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_size, + size_t link, + U32 count, + U32 const max_depth) +{ + /* Offset data pointer. This method is only called at depth 2 */ + const BYTE* data_src = data_block + 2; + size_t limit = block_size - 2; + size_t st_index = 0; + + do + { + if (link < limit) { + size_t const radix_8 = data_src[link]; + size_t const prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + SetMatchLinkAndLength(prev, (U32)link, 3); + } + else { + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)link; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + tbl->tails_8[radix_8].prev_index = (U32)link; + } + link = GetMatchLink(link); + } while (--count > 0); + for (size_t i = 0; i < st_index; ++i) { + tbl->stack[i].count = tbl->tails_8[tbl->stack[i].count].list_count; + } + memset(tbl->tails_8, 0xFF, sizeof(tbl->tails_8)); + while (st_index > 0) { + --st_index; + U32 list_count = tbl->stack[st_index].count; + if (list_count < 2) { + /* Nothing to do */ + continue; + } + if (st_index > STACK_SIZE - RADIX8_TABLE_SIZE + && st_index > STACK_SIZE - list_count) + { + /* Potential stack overflow. Rare. */ + continue; + } + link = tbl->stack[st_index].head; + /* The current depth */ + U32 depth = GetMatchLength(link); + if (depth >= max_depth) + continue; + data_src = data_block + depth; + limit = block_size - depth; + /* Next depth for 1 extra char */ + ++depth; + size_t prev_st_index = st_index; + do { + if (link < limit) { + size_t const radix_8 = data_src[link]; + size_t const prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + SetMatchLinkAndLength(prev, (U32)link, depth); + } + else { + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)link; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + tbl->tails_8[radix_8].prev_index = (U32)link; + } + link = GetMatchLink(link); + } while (--list_count != 0); + for (size_t i = prev_st_index; i < st_index; ++i) { + tbl->stack[i].count = tbl->tails_8[tbl->stack[i].count].list_count; + } + memset(tbl->tails_8, 0xFF, sizeof(tbl->tails_8)); + } +} + +#endif /* RMF_REFERENCE */ + +/* Atomically take a list from the head table */ +static ptrdiff_t RMF_getNextList_mt(FL2_matchTable* const tbl) +{ + if (tbl->st_index < tbl->end_index) { + long pos = FL2_atomic_increment(tbl->st_index); + if (pos < tbl->end_index) + return pos; + } + return -1; +} + +/* Non-atomically take a list from the head table */ +static ptrdiff_t RMF_getNextList_st(FL2_matchTable* const tbl) +{ + if (tbl->st_index < tbl->end_index) { + long pos = FL2_nonAtomic_increment(tbl->st_index); + if (pos < tbl->end_index) + return pos; + } + return -1; +} + +/* Iterate the head table concurrently with other threads, and recurse each list until max_depth is reached */ +void +#ifdef RMF_BITPACK +RMF_bitpackBuildTable +#else +RMF_structuredBuildTable +#endif +(FL2_matchTable* const tbl, + size_t const job, + unsigned const multi_thread, + FL2_dataBlock const block) +{ + if (block.end == 0) + return; + + unsigned const best = !tbl->params.divide_and_conquer; + unsigned const max_depth = MIN(tbl->params.depth, STRUCTURED_MAX_LENGTH) & ~1; + size_t bounded_start = max_depth + MAX_READ_BEYOND_DEPTH; + bounded_start = block.end - MIN(block.end, bounded_start); + ptrdiff_t next_progress = (job == 0) ? 0 : RADIX16_TABLE_SIZE; + ptrdiff_t(*getNextList)(FL2_matchTable* const tbl) + = multi_thread ? RMF_getNextList_mt : RMF_getNextList_st; + + for (;;) + { + /* Get the next to process */ + ptrdiff_t pos = getNextList(tbl); + + if (pos < 0) + break; + + while (next_progress < pos) { + /* initial value of next_progress ensures only thread 0 executes this */ + tbl->progress += tbl->list_heads[tbl->stack[next_progress]].count; + ++next_progress; + } + pos = tbl->stack[pos]; + RMF_tableHead list_head = tbl->list_heads[pos]; + tbl->list_heads[pos].head = RADIX_NULL_LINK; + if (list_head.count < 2 || list_head.head < block.start) + continue; + +#ifdef RMF_REFERENCE + if (tbl->params.use_ref_mf) { + RMF_recurseListsReference(tbl->builders[job], block.data, block.end, list_head.head, list_head.count, max_depth); + continue; + } +#endif + if (list_head.head >= bounded_start) { + RMF_recurseListsBound(tbl->builders[job], block.data, block.end, &list_head, max_depth); + if (list_head.count < 2 || list_head.head < block.start) + continue; + } + if (best && list_head.count > tbl->builders[job]->match_buffer_limit) + { + /* Not worth buffering or too long */ + RMF_recurseLists16(tbl->builders[job], block.data, block.start, list_head.head, list_head.count, max_depth); + } + else { + RMF_recurseListsBuffered(tbl->builders[job], block.data, block.start, list_head.head, 2, (BYTE)max_depth, list_head.count, 0); + } + } +} + +int +#ifdef RMF_BITPACK +RMF_bitpackIntegrityCheck +#else +RMF_structuredIntegrityCheck +#endif +(const FL2_matchTable* const tbl, const BYTE* const data, size_t pos, size_t const end, unsigned max_depth) +{ + max_depth &= ~1; + int err = 0; + for (pos += !pos; pos < end; ++pos) { + if (IsNull(pos)) + continue; + U32 const link = GetMatchLink(pos); + if (link >= pos) { + printf("Forward link at %X to %u\r\n", (U32)pos, link); + err = 1; + continue; + } + U32 const length = GetMatchLength(pos); + if (pos && length < RADIX_MAX_LENGTH && link - 1 == GetMatchLink(pos - 1) && length + 1 == GetMatchLength(pos - 1)) + continue; + U32 len_test = 0; + U32 const limit = MIN((U32)(end - pos), RADIX_MAX_LENGTH); + for (; len_test < limit && data[link + len_test] == data[pos + len_test]; ++len_test) { + } + if (len_test < length) { + printf("Failed integrity check: pos %X, length %u, actual %u\r\n", (U32)pos, length, len_test); + err = 1; + } + if (length < max_depth && len_test > length) + /* These occur occasionally due to splitting of chains in the buffer when long repeats are present */ + printf("Shortened match at %X: %u of %u\r\n", (U32)pos, length, len_test); + } + return err; +} diff --git a/builtins/flzma2/radix_get.h b/builtins/flzma2/radix_get.h new file mode 100644 index 0000000000000..3a6cfa5716eac --- /dev/null +++ b/builtins/flzma2/radix_get.h @@ -0,0 +1,202 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#ifndef FL2_RADIX_GET_H_ +#define FL2_RADIX_GET_H_ + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct +{ + U32 length; + U32 dist; +} RMF_match; + +static size_t RMF_bitpackExtendMatch(const BYTE* const data, + const U32* const table, + ptrdiff_t const start_index, + ptrdiff_t limit, + U32 const link, + size_t const length) +{ + ptrdiff_t end_index = start_index + length; + ptrdiff_t const dist = start_index - link; + + if (limit > start_index + (ptrdiff_t)kMatchLenMax) + limit = start_index + kMatchLenMax; + + while (end_index < limit && end_index - (ptrdiff_t)(table[end_index] & RADIX_LINK_MASK) == dist) + end_index += table[end_index] >> RADIX_LINK_BITS; + + if (end_index >= limit) { + DEBUGLOG(7, "RMF_bitpackExtendMatch : pos %u, link %u, init length %u, full length %u", (U32)start_index, link, (U32)length, (U32)(limit - start_index)); + return limit - start_index; + } + + while (end_index < limit && data[end_index - dist] == data[end_index]) + ++end_index; + + DEBUGLOG(7, "RMF_bitpackExtendMatch : pos %u, link %u, init length %u, full length %u", (U32)start_index, link, (U32)length, (U32)(end_index - start_index)); + return end_index - start_index; +} + +#define GetMatchLink(table, pos) ((const RMF_unit*)(table))[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] + +#define GetMatchLength(table, pos) ((const RMF_unit*)(table))[(pos) >> UNIT_BITS].lengths[(pos) & UNIT_MASK] + +static size_t RMF_structuredExtendMatch(const BYTE* const data, + const U32* const table, + ptrdiff_t const start_index, + ptrdiff_t limit, + U32 const link, + size_t const length) +{ + ptrdiff_t end_index = start_index + length; + ptrdiff_t const dist = start_index - link; + + if (limit > start_index + (ptrdiff_t)kMatchLenMax) + limit = start_index + kMatchLenMax; + + while (end_index < limit && end_index - (ptrdiff_t)GetMatchLink(table, end_index) == dist) + end_index += GetMatchLength(table, end_index); + + if (end_index >= limit) { + DEBUGLOG(7, "RMF_structuredExtendMatch : pos %u, link %u, init length %u, full length %u", (U32)start_index, link, (U32)length, (U32)(limit - start_index)); + return limit - start_index; + } + + while (end_index < limit && data[end_index - dist] == data[end_index]) + ++end_index; + + DEBUGLOG(7, "RMF_structuredExtendMatch : pos %u, link %u, init length %u, full length %u", (U32)start_index, link, (U32)length, (U32)(end_index - start_index)); + return end_index - start_index; +} + +FORCE_INLINE_TEMPLATE +RMF_match RMF_getMatch(FL2_dataBlock block, + FL2_matchTable* tbl, + unsigned max_depth, + int structTbl, + size_t pos) +{ + if (structTbl) + { + U32 const link = GetMatchLink(tbl->table, pos); + + RMF_match match; + match.length = 0; + + if (link == RADIX_NULL_LINK) + return match; + + size_t const length = GetMatchLength(tbl->table, pos); + size_t const dist = pos - link - 1; + + if (length == max_depth || length == STRUCTURED_MAX_LENGTH /* from HandleRepeat */) + match.length = (U32)RMF_structuredExtendMatch(block.data, tbl->table, pos, block.end, link, length); + else + match.length = (U32)length; + + match.dist = (U32)dist; + + return match; + } + else { + U32 link = tbl->table[pos]; + + RMF_match match; + match.length = 0; + + if (link == RADIX_NULL_LINK) + return match; + + size_t const length = link >> RADIX_LINK_BITS; + link &= RADIX_LINK_MASK; + size_t const dist = pos - link - 1; + + if (length == max_depth || length == BITPACK_MAX_LENGTH /* from HandleRepeat */) + match.length = (U32)RMF_bitpackExtendMatch(block.data, tbl->table, pos, block.end, link, length); + else + match.length = (U32)length; + + match.dist = (U32)dist; + + return match; + } +} + +FORCE_INLINE_TEMPLATE +RMF_match RMF_getNextMatch(FL2_dataBlock block, + FL2_matchTable* tbl, + unsigned max_depth, + int structTbl, + size_t pos) +{ + if (structTbl) + { + U32 const link = GetMatchLink(tbl->table, pos); + + RMF_match match; + match.length = 0; + + if (link == RADIX_NULL_LINK) + return match; + + size_t const length = GetMatchLength(tbl->table, pos); + size_t const dist = pos - link - 1; + + /* same distance, one byte shorter */ + if (link - 1 == GetMatchLink(tbl->table, pos - 1)) + return match; + + if (length == max_depth || length == STRUCTURED_MAX_LENGTH /* from HandleRepeat */) + match.length = (U32)RMF_structuredExtendMatch(block.data, tbl->table, pos, block.end, link, length); + else + match.length = (U32)length; + + match.dist = (U32)dist; + + return match; + } + else { + U32 link = tbl->table[pos]; + + RMF_match match; + match.length = 0; + + if (link == RADIX_NULL_LINK) + return match; + + size_t const length = link >> RADIX_LINK_BITS; + link &= RADIX_LINK_MASK; + size_t const dist = pos - link - 1; + + /* same distance, one byte shorter */ + if (link - 1 == (tbl->table[pos - 1] & RADIX_LINK_MASK)) + return match; + + if (length == max_depth || length == BITPACK_MAX_LENGTH /* from HandleRepeat */) + match.length = (U32)RMF_bitpackExtendMatch(block.data, tbl->table, pos, block.end, link, length); + else + match.length = (U32)length; + + match.dist = (U32)dist; + + return match; + } +} + +#if defined (__cplusplus) +} +#endif + +#endif /* FL2_RADIX_GET_H_ */ \ No newline at end of file diff --git a/builtins/flzma2/radix_internal.h b/builtins/flzma2/radix_internal.h new file mode 100644 index 0000000000000..9bede0ca5ee7a --- /dev/null +++ b/builtins/flzma2/radix_internal.h @@ -0,0 +1,148 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#ifndef RADIX_INTERNAL_H +#define RADIX_INTERNAL_H + +#include "atomic.h" +#include "radix_mf.h" + +#if defined(FL2_XZ_BUILD) && defined(TUKLIB_FAST_UNALIGNED_ACCESS) +# define MEM_read32(a) (*(const U32*)(a)) +#endif + +#if defined (__cplusplus) +extern "C" { +#endif + +#define DICTIONARY_LOG_MIN 12U +#define DICTIONARY_LOG_MAX_64 30U +#define DICTIONARY_LOG_MAX_32 27U +#define DICTIONARY_SIZE_MIN ((size_t)1 << DICTIONARY_LOG_MIN) +#define DICTIONARY_SIZE_MAX_64 ((size_t)1 << DICTIONARY_LOG_MAX_64) +#define DICTIONARY_SIZE_MAX_32 ((size_t)1 << DICTIONARY_LOG_MAX_32) +#define MAX_REPEAT 24 +#define RADIX16_TABLE_SIZE ((size_t)1 << 16) +#define RADIX8_TABLE_SIZE ((size_t)1 << 8) +#define STACK_SIZE (RADIX16_TABLE_SIZE * 3) +#define MAX_BRUTE_FORCE_LIST_SIZE 5 +#define BUFFER_LINK_MASK 0xFFFFFFU +#define MATCH_BUFFER_OVERLAP 6 +#define BITPACK_MAX_LENGTH 63U +#define STRUCTURED_MAX_LENGTH 255U + +#define RADIX_LINK_BITS 26 +#define RADIX_LINK_MASK ((1U << RADIX_LINK_BITS) - 1) +#define RADIX_NULL_LINK 0xFFFFFFFFU + +#define UNIT_BITS 2 +#define UNIT_MASK ((1U << UNIT_BITS) - 1) + +#define RADIX_CANCEL_INDEX (long)(RADIX16_TABLE_SIZE + FL2_MAXTHREADS + 2) + +typedef struct +{ + U32 head; + U32 count; +} RMF_tableHead; + +union src_data_u { + BYTE chars[4]; + U32 u32; +}; + +typedef struct +{ + U32 from; + union src_data_u src; + U32 next; +} RMF_buildMatch; + +typedef struct +{ + U32 prev_index; + U32 list_count; +} RMF_listTail; + +typedef struct +{ + U32 links[1 << UNIT_BITS]; + BYTE lengths[1 << UNIT_BITS]; +} RMF_unit; + +typedef struct +{ + unsigned max_len; + U32* table; + size_t match_buffer_size; + size_t match_buffer_limit; + RMF_listTail tails_8[RADIX8_TABLE_SIZE]; + RMF_tableHead stack[STACK_SIZE]; + RMF_listTail tails_16[RADIX16_TABLE_SIZE]; + RMF_buildMatch match_buffer[1]; +} RMF_builder; + +struct FL2_matchTable_s +{ + FL2_atomic st_index; + long end_index; + int is_struct; + int alloc_struct; + unsigned thread_count; + size_t unreduced_dict_size; + size_t progress; + RMF_parameters params; + RMF_builder** builders; + U32 stack[RADIX16_TABLE_SIZE]; + RMF_tableHead list_heads[RADIX16_TABLE_SIZE]; + U32 table[1]; +}; + +void RMF_bitpackInit(struct FL2_matchTable_s* const tbl, const void* data, size_t const end); +void RMF_structuredInit(struct FL2_matchTable_s* const tbl, const void* data, size_t const end); +void RMF_bitpackBuildTable(struct FL2_matchTable_s* const tbl, + size_t const job, + unsigned const multi_thread, + FL2_dataBlock const block); +void RMF_structuredBuildTable(struct FL2_matchTable_s* const tbl, + size_t const job, + unsigned const multi_thread, + FL2_dataBlock const block); +void RMF_recurseListChunk(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + U32 const depth, + U32 const max_depth, + U32 const list_count, + size_t const stack_base); +int RMF_bitpackIntegrityCheck(const struct FL2_matchTable_s* const tbl, const BYTE* const data, size_t pos, size_t const end, unsigned max_depth); +int RMF_structuredIntegrityCheck(const struct FL2_matchTable_s* const tbl, const BYTE* const data, size_t pos, size_t const end, unsigned max_depth); +void RMF_bitpackLimitLengths(struct FL2_matchTable_s* const tbl, size_t const pos); +void RMF_structuredLimitLengths(struct FL2_matchTable_s* const tbl, size_t const pos); +BYTE* RMF_bitpackAsOutputBuffer(struct FL2_matchTable_s* const tbl, size_t const pos); +BYTE* RMF_structuredAsOutputBuffer(struct FL2_matchTable_s* const tbl, size_t const pos); +size_t RMF_bitpackGetMatch(const struct FL2_matchTable_s* const tbl, + const BYTE* const data, + size_t const pos, + size_t const limit, + unsigned const max_depth, + size_t* const offset_ptr); +size_t RMF_structuredGetMatch(const struct FL2_matchTable_s* const tbl, + const BYTE* const data, + size_t const pos, + size_t const limit, + unsigned const max_depth, + size_t* const offset_ptr); + +#if defined (__cplusplus) +} +#endif + +#endif /* RADIX_INTERNAL_H */ \ No newline at end of file diff --git a/builtins/flzma2/radix_mf.c b/builtins/flzma2/radix_mf.c new file mode 100644 index 0000000000000..346f894561372 --- /dev/null +++ b/builtins/flzma2/radix_mf.c @@ -0,0 +1,736 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include /* size_t, ptrdiff_t */ +#include /* malloc, free */ +#include "fast-lzma2.h" +#include "fl2_errors.h" +#include "mem.h" /* U32, U64, MEM_64bits */ +#include "fl2_internal.h" +#include "radix_internal.h" + +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 407) +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" /* warning: 'rpt_head_next' may be used uninitialized in this function */ +#elif defined(_MSC_VER) +# pragma warning(disable : 4701) /* warning: 'rpt_head_next' may be used uninitialized in this function */ +#endif + +#define MATCH_BUFFER_SHIFT 8; +#define MATCH_BUFFER_ELBOW_BITS 17 +#define MATCH_BUFFER_ELBOW (1UL << MATCH_BUFFER_ELBOW_BITS) +#define MIN_MATCH_BUFFER_SIZE 256U /* min buffer size at least FL2_SEARCH_DEPTH_MAX + 2 for bounded build */ +#define MAX_MATCH_BUFFER_SIZE (1UL << 24) /* max buffer size constrained by 24-bit link values */ + +static void RMF_initTailTable(RMF_builder* const tbl) +{ + for (size_t i = 0; i < RADIX8_TABLE_SIZE; i += 2) { + tbl->tails_8[i].prev_index = RADIX_NULL_LINK; + tbl->tails_8[i + 1].prev_index = RADIX_NULL_LINK; + } + for (size_t i = 0; i < RADIX16_TABLE_SIZE; i += 2) { + tbl->tails_16[i].prev_index = RADIX_NULL_LINK; + tbl->tails_16[i + 1].prev_index = RADIX_NULL_LINK; + } +} + +static RMF_builder* RMF_createBuilder(size_t match_buffer_size) +{ + match_buffer_size = MIN(match_buffer_size, MAX_MATCH_BUFFER_SIZE); + match_buffer_size = MAX(match_buffer_size, MIN_MATCH_BUFFER_SIZE); + + RMF_builder* const builder = malloc( + sizeof(RMF_builder) + (match_buffer_size - 1) * sizeof(RMF_buildMatch)); + + if (builder == NULL) + return NULL; + + builder->match_buffer_size = match_buffer_size; + builder->match_buffer_limit = match_buffer_size; + + RMF_initTailTable(builder); + + return builder; +} + +static void RMF_freeBuilderTable(RMF_builder** const builders, unsigned const size) +{ + if (builders == NULL) + return; + + for (unsigned i = 0; i < size; ++i) + free(builders[i]); + + free(builders); +} + +/* RMF_createBuilderTable() : + * Create one match table builder object per thread. + * max_len : maximum match length supported by the table structure + * size : number of threads + */ +static RMF_builder** RMF_createBuilderTable(U32* const match_table, size_t const match_buffer_size, unsigned const max_len, unsigned const size) +{ + DEBUGLOG(3, "RMF_createBuilderTable : match_buffer_size %u, builders %u", (U32)match_buffer_size, size); + + RMF_builder** const builders = malloc(size * sizeof(RMF_builder*)); + + if (builders == NULL) + return NULL; + + for (unsigned i = 0; i < size; ++i) + builders[i] = NULL; + + for (unsigned i = 0; i < size; ++i) { + builders[i] = RMF_createBuilder(match_buffer_size); + if (builders[i] == NULL) { + RMF_freeBuilderTable(builders, i); + return NULL; + } + builders[i]->table = match_table; + builders[i]->max_len = max_len; + } + return builders; +} + +static int RMF_isStruct(size_t const dictionary_size) +{ + return dictionary_size > ((size_t)1 << RADIX_LINK_BITS); +} + +/* RMF_clampParams() : +* Make param values within valid range. +* Return : valid RMF_parameters */ +static RMF_parameters RMF_clampParams(RMF_parameters params) +{ +# define CLAMP(val,min,max) { \ + if (val<(min)) val=(min); \ + else if (val>(max)) val=(max); \ + } +# define MAXCLAMP(val,max) { \ + if (val>(max)) val=(max); \ + } + CLAMP(params.dictionary_size, DICTIONARY_SIZE_MIN, MEM_64bits() ? DICTIONARY_SIZE_MAX_64 : DICTIONARY_SIZE_MAX_32); + MAXCLAMP(params.match_buffer_resize, FL2_BUFFER_RESIZE_MAX); + MAXCLAMP(params.overlap_fraction, FL2_BLOCK_OVERLAP_MAX); + CLAMP(params.depth, FL2_SEARCH_DEPTH_MIN, FL2_SEARCH_DEPTH_MAX); + return params; +# undef MAXCLAMP +# undef CLAMP +} + +static size_t RMF_calBufSize(size_t dictionary_size, unsigned buffer_resize) +{ + size_t buffer_size = dictionary_size >> MATCH_BUFFER_SHIFT; + if (buffer_size > MATCH_BUFFER_ELBOW) { + size_t extra = 0; + unsigned n = MATCH_BUFFER_ELBOW_BITS - 1; + for (; (4UL << n) <= buffer_size; ++n) + extra += MATCH_BUFFER_ELBOW >> 4; + if((3UL << n) <= buffer_size) + extra += MATCH_BUFFER_ELBOW >> 5; + buffer_size = MATCH_BUFFER_ELBOW + extra; + } + if (buffer_resize > 2) + buffer_size += buffer_size >> (4 - buffer_resize); + else if (buffer_resize < 2) + buffer_size -= buffer_size >> (buffer_resize + 1); + return buffer_size; +} + +/* RMF_applyParameters_internal() : + * Set parameters to those specified. + * Create a builder table if none exists. Free an existing one if incompatible. + * Set match_buffer_limit and max supported match length. + * Returns an error if dictionary won't fit. + */ +static size_t RMF_applyParameters_internal(FL2_matchTable* const tbl, const RMF_parameters* const params) +{ + int const is_struct = RMF_isStruct(params->dictionary_size); + size_t const dictionary_size = tbl->params.dictionary_size; + /* dictionary is allocated with the struct and is immutable */ + if (params->dictionary_size > tbl->params.dictionary_size + || (params->dictionary_size == tbl->params.dictionary_size && is_struct > tbl->alloc_struct)) + return FL2_ERROR(parameter_unsupported); + + size_t const match_buffer_size = RMF_calBufSize(tbl->unreduced_dict_size, params->match_buffer_resize); + tbl->params = *params; + tbl->params.dictionary_size = dictionary_size; + tbl->is_struct = is_struct; + if (tbl->builders == NULL + || match_buffer_size > tbl->builders[0]->match_buffer_size) + { + RMF_freeBuilderTable(tbl->builders, tbl->thread_count); + tbl->builders = RMF_createBuilderTable(tbl->table, match_buffer_size, tbl->is_struct ? STRUCTURED_MAX_LENGTH : BITPACK_MAX_LENGTH, tbl->thread_count); + if (tbl->builders == NULL) { + return FL2_ERROR(memory_allocation); + } + } + else { + for (unsigned i = 0; i < tbl->thread_count; ++i) { + tbl->builders[i]->match_buffer_limit = match_buffer_size; + tbl->builders[i]->max_len = tbl->is_struct ? STRUCTURED_MAX_LENGTH : BITPACK_MAX_LENGTH; + } + } + return 0; +} + +/* RMF_reduceDict() : + * Reduce dictionary and match buffer size if the total input size is known and < dictionary_size. + */ +static void RMF_reduceDict(RMF_parameters* const params, size_t const dict_reduce) +{ + if (dict_reduce) + params->dictionary_size = MIN(params->dictionary_size, MAX(dict_reduce, DICTIONARY_SIZE_MIN)); +} + +static void RMF_initListHeads(FL2_matchTable* const tbl) +{ + for (size_t i = 0; i < RADIX16_TABLE_SIZE; i += 2) { + tbl->list_heads[i].head = RADIX_NULL_LINK; + tbl->list_heads[i].count = 0; + tbl->list_heads[i + 1].head = RADIX_NULL_LINK; + tbl->list_heads[i + 1].count = 0; + } +} + +/* RMF_createMatchTable() : + * Create a match table. Reduce the dict size to input size if possible. + * A thread_count of 0 will be raised to 1. + */ +FL2_matchTable* RMF_createMatchTable(const RMF_parameters* const p, size_t const dict_reduce, unsigned const thread_count) +{ + RMF_parameters params = RMF_clampParams(*p); + size_t unreduced_dict_size = params.dictionary_size; + RMF_reduceDict(¶ms, dict_reduce); + + int const is_struct = RMF_isStruct(params.dictionary_size); + size_t dictionary_size = params.dictionary_size; + + DEBUGLOG(3, "RMF_createMatchTable : is_struct %d, dict %u", is_struct, (U32)dictionary_size); + + size_t const table_bytes = is_struct ? ((dictionary_size + 3U) / 4U) * sizeof(RMF_unit) + : dictionary_size * sizeof(U32); + FL2_matchTable* const tbl = malloc(sizeof(FL2_matchTable) + table_bytes - sizeof(U32)); + if (tbl == NULL) + return NULL; + + tbl->is_struct = is_struct; + tbl->alloc_struct = is_struct; + tbl->thread_count = thread_count + !thread_count; + tbl->params = params; + tbl->unreduced_dict_size = unreduced_dict_size; + tbl->builders = NULL; + + RMF_applyParameters_internal(tbl, ¶ms); + + RMF_initListHeads(tbl); + + RMF_initProgress(tbl); + + return tbl; +} + +void RMF_freeMatchTable(FL2_matchTable* const tbl) +{ + if (tbl == NULL) + return; + + DEBUGLOG(3, "RMF_freeMatchTable"); + + RMF_freeBuilderTable(tbl->builders, tbl->thread_count); + free(tbl); +} + +BYTE RMF_compatibleParameters(const FL2_matchTable* const tbl, const RMF_parameters * const p, size_t const dict_reduce) +{ + RMF_parameters params = RMF_clampParams(*p); + RMF_reduceDict(¶ms, dict_reduce); + return tbl->params.dictionary_size > params.dictionary_size + || (tbl->params.dictionary_size == params.dictionary_size && tbl->alloc_struct >= RMF_isStruct(params.dictionary_size)); +} + +size_t RMF_applyParameters(FL2_matchTable* const tbl, const RMF_parameters* const p, size_t const dict_reduce) +{ + RMF_parameters params = RMF_clampParams(*p); + RMF_reduceDict(¶ms, dict_reduce); + return RMF_applyParameters_internal(tbl, ¶ms); +} + +size_t RMF_threadCount(const FL2_matchTable* const tbl) +{ + return tbl->thread_count; +} + +void RMF_initProgress(FL2_matchTable * const tbl) +{ + if (tbl != NULL) + tbl->progress = 0; +} + +void RMF_initTable(FL2_matchTable* const tbl, const void* const data, size_t const end) +{ + DEBUGLOG(5, "RMF_initTable : size %u", (U32)end); + + tbl->st_index = ATOMIC_INITIAL_VALUE; + + if (tbl->is_struct) + RMF_structuredInit(tbl, data, end); + else + RMF_bitpackInit(tbl, data, end); +} + +static void RMF_handleRepeat(RMF_buildMatch* const match_buffer, + const BYTE* const data_block, + size_t const next, + U32 count, + U32 const rpt_len, + U32 const depth, + U32 const max_len) +{ + size_t pos = next; + U32 length = depth + rpt_len; + + const BYTE* const data = data_block + match_buffer[pos].from; + const BYTE* const data_2 = data - rpt_len; + + while (data[length] == data_2[length] && length < max_len) + ++length; + + for (; length <= max_len && count; --count) { + size_t next_i = match_buffer[pos].next & 0xFFFFFF; + match_buffer[pos].next = (U32)next_i | (length << 24); + length += rpt_len; + pos = next_i; + } + for (; count; --count) { + size_t next_i = match_buffer[pos].next & 0xFFFFFF; + match_buffer[pos].next = (U32)next_i | (max_len << 24); + pos = next_i; + } +} + +typedef struct +{ + size_t pos; + const BYTE* data_src; + union src_data_u src; +} BruteForceMatch; + +static void RMF_bruteForceBuffered(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + size_t pos, + size_t const list_count, + size_t const slot, + size_t const depth, + size_t const max_depth) +{ + BruteForceMatch buffer[MAX_BRUTE_FORCE_LIST_SIZE + 1]; + const BYTE* const data_src = data_block + depth; + size_t const limit = max_depth - depth; + const BYTE* const start = data_src + block_start; + size_t i = 0; + for (;;) { + /* Load all locations from the match buffer */ + buffer[i].pos = pos; + buffer[i].data_src = data_src + tbl->match_buffer[pos].from; + buffer[i].src.u32 = tbl->match_buffer[pos].src.u32; + + if (++i >= list_count) + break; + + pos = tbl->match_buffer[pos].next & 0xFFFFFF; + } + i = 0; + do { + size_t longest = 0; + size_t j = i + 1; + size_t longest_index = j; + const BYTE* const data = buffer[i].data_src; + do { + /* Begin with the remaining chars pulled from the match buffer */ + size_t len_test = slot; + while (len_test < 4 && buffer[i].src.chars[len_test] == buffer[j].src.chars[len_test] && len_test - slot < limit) + ++len_test; + + len_test -= slot; + if (len_test) { + /* Complete the match length count in the raw input buffer */ + const BYTE* data_2 = buffer[j].data_src; + while (data[len_test] == data_2[len_test] && len_test < limit) + ++len_test; + } + if (len_test > longest) { + longest_index = j; + longest = len_test; + if (len_test >= limit) + break; + } + } while (++j < list_count); + if (longest > 0) { + /* If the existing match was extended, store the new link and length info in the match buffer */ + pos = buffer[i].pos; + tbl->match_buffer[pos].next = (U32)(buffer[longest_index].pos | ((depth + longest) << 24)); + } + ++i; + } while (i < list_count - 1 && buffer[i].data_src >= start); +} + +/* Lengthen and divide buffered chains into smaller chains, save them on a stack and process in turn. + * The match finder spends most of its time here. + */ +FORCE_INLINE_TEMPLATE +void RMF_recurseListChunk_generic(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + U32 depth, + U32 const max_depth, + U32 list_count, + size_t const stack_base) +{ + U32 const base_depth = depth; + size_t st_index = stack_base; + size_t pos = 0; + ++depth; + /* The last element is done separately and won't be copied back at the end */ + --list_count; + do { + size_t const radix_8 = tbl->match_buffer[pos].src.chars[0]; + /* Seen this char before? */ + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + /* Link the previous occurrence to this one and record the new length */ + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tbl->tails_8[radix_8].list_count = 1; + /* Add the new sub list to the stack */ + tbl->stack[st_index].head = (U32)pos; + /* This will be converted to a count at the end */ + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + ++pos; + } while (pos < list_count); + + { /* Do the last element */ + size_t const radix_8 = tbl->match_buffer[pos].src.chars[0]; + /* Nothing to do if there was no previous */ + U32 const prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + } + /* Convert radix values on the stack to counts and reset any used tail slots */ + for (size_t j = stack_base; j < st_index; ++j) { + tbl->tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + tbl->stack[j].count = (U32)tbl->tails_8[tbl->stack[j].count].list_count; + } + while (st_index > stack_base) { + /* Pop an item off the stack */ + --st_index; + list_count = tbl->stack[st_index].count; + if (list_count < 2) { + /* Nothing to match with */ + continue; + } + pos = tbl->stack[st_index].head; + size_t link = tbl->match_buffer[pos].from; + if (link < block_start) { + /* Chain starts in the overlap region which is already encoded */ + continue; + } + /* Check stack space. The first comparison is unnecessary but it's a constant so should be faster */ + if (st_index > STACK_SIZE - RADIX8_TABLE_SIZE + && st_index > STACK_SIZE - list_count) + { + /* Stack may not be able to fit all possible new items. This is very rare. */ + continue; + } + depth = tbl->match_buffer[pos].next >> 24; + /* Index into the 4-byte pre-loaded input char cache */ + size_t slot = (depth - base_depth) & 3; + if (list_count <= MAX_BRUTE_FORCE_LIST_SIZE) { + /* Quicker to use brute force, each string compared with all previous strings */ + RMF_bruteForceBuffered(tbl, + data_block, + block_start, + pos, + list_count, + slot, + depth, + max_depth); + continue; + } + /* check for repeats at depth 4,8,16,32 etc unless depth is near max_depth */ + U32 const test = max_depth != 6 && ((depth & 3) == 0) + && (depth & (depth - 1)) == 0 + && (max_depth >= depth + (depth >> 1)); + ++depth; + /* Create an offset data buffer pointer for reading the next bytes */ + const BYTE* const data_src = data_block + depth; + /* Last pass is done separately */ + if (!test && depth < max_depth) { + size_t const prev_st_index = st_index; + /* Last element done separately */ + --list_count; + /* If slot is 3 then chars need to be loaded. */ + if (slot == 3 && max_depth != 6) do { + size_t const radix_8 = tbl->match_buffer[pos].src.chars[3]; + size_t const next_index = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + /* Pre-load the next link and data bytes. On some hardware execution can continue + * ahead while the data is retrieved if no operations except move are done on the data. */ + tbl->match_buffer[pos].src.u32 = MEM_read32(data_src + link); + size_t const next_link = tbl->match_buffer[next_index].from; + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + /* This char has occurred before in the chain. Link the previous (> pos) occurance with this */ + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + /* First occurrence in the chain */ + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)pos; + /* Save the char as a reference to load the count at the end */ + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + pos = next_index; + link = next_link; + } while (--list_count != 0); + else do { + size_t const radix_8 = tbl->match_buffer[pos].src.chars[slot]; + size_t const next_index = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + /* Pre-load the next link to avoid waiting for RAM access */ + size_t const next_link = tbl->match_buffer[next_index].from; + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)pos; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + pos = next_index; + link = next_link; + } while (--list_count != 0); + + size_t const radix_8 = tbl->match_buffer[pos].src.chars[slot]; + U32 const prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + if (slot == 3) + tbl->match_buffer[pos].src.u32 = MEM_read32(data_src + link); + + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + for (size_t j = prev_st_index; j < st_index; ++j) { + tbl->tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + tbl->stack[j].count = (U32)tbl->tails_8[tbl->stack[j].count].list_count; + } + } + else if (test) { + S32 rpt = -1; + size_t rpt_head_next; + U32 rpt_dist = 0; + size_t const prev_st_index = st_index; + U32 const rpt_depth = depth - 1; + /* Last element done separately */ + --list_count; + do { + size_t const radix_8 = tbl->match_buffer[pos].src.chars[slot]; + size_t const next_index = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + size_t const next_link = tbl->match_buffer[next_index].from; + if ((link - next_link) > rpt_depth) { + if (rpt > 0) + RMF_handleRepeat(tbl->match_buffer, data_block, rpt_head_next, rpt, rpt_dist, rpt_depth, tbl->max_len); + + rpt = -1; + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)pos; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + pos = next_index; + link = next_link; + } + else { + U32 const dist = (U32)(link - next_link); + if (rpt < 0 || dist != rpt_dist) { + if (rpt > 0) + RMF_handleRepeat(tbl->match_buffer, data_block, rpt_head_next, rpt, rpt_dist, rpt_depth, tbl->max_len); + + rpt = 0; + rpt_head_next = next_index; + rpt_dist = dist; + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tbl->tails_8[radix_8].list_count = 1; + tbl->stack[st_index].head = (U32)pos; + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + } + else { + ++rpt; + } + pos = next_index; + link = next_link; + } + } while (--list_count != 0); + + if (rpt > 0) + RMF_handleRepeat(tbl->match_buffer, data_block, rpt_head_next, rpt, rpt_dist, rpt_depth, tbl->max_len); + + size_t const radix_8 = tbl->match_buffer[pos].src.chars[slot]; + U32 const prev = tbl->tails_8[radix_8].prev_index; + if (prev != RADIX_NULL_LINK) { + if (slot == 3) { + tbl->match_buffer[pos].src.u32 = MEM_read32(data_src + link); + } + ++tbl->tails_8[radix_8].list_count; + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + for (size_t j = prev_st_index; j < st_index; ++j) { + tbl->tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + tbl->stack[j].count = (U32)tbl->tails_8[tbl->stack[j].count].list_count; + } + } + else { + size_t const prev_st_index = st_index; + /* The last pass at max_depth */ + do { + size_t const radix_8 = tbl->match_buffer[pos].src.chars[slot]; + size_t const next_index = tbl->match_buffer[pos].next & BUFFER_LINK_MASK; + /* Pre-load the next link. */ + /* The last element in tbl->match_buffer is circular so this is never an access violation. */ + size_t const next_link = tbl->match_buffer[next_index].from; + U32 const prev = tbl->tails_8[radix_8].prev_index; + tbl->tails_8[radix_8].prev_index = (U32)pos; + if (prev != RADIX_NULL_LINK) { + tbl->match_buffer[prev].next = (U32)pos | (depth << 24); + } + else { + tbl->stack[st_index].count = (U32)radix_8; + ++st_index; + } + pos = next_index; + link = next_link; + } while (--list_count != 0); + for (size_t j = prev_st_index; j < st_index; ++j) { + tbl->tails_8[tbl->stack[j].count].prev_index = RADIX_NULL_LINK; + } + st_index = prev_st_index; + } + } +} + +void RMF_recurseListChunk(RMF_builder* const tbl, + const BYTE* const data_block, + size_t const block_start, + U32 const depth, + U32 const max_depth, + U32 const list_count, + size_t const stack_base) +{ + if (list_count < 2) + return; + /* Template-like inline functions */ + if (list_count <= MAX_BRUTE_FORCE_LIST_SIZE) + RMF_bruteForceBuffered(tbl, data_block, block_start, 0, list_count, 0, depth, max_depth); + else if (max_depth > 6) + RMF_recurseListChunk_generic(tbl, data_block, block_start, depth, max_depth, list_count, stack_base); + else + RMF_recurseListChunk_generic(tbl, data_block, block_start, depth, 6, list_count, stack_base); +} + +/* Iterate the head table concurrently with other threads, and recurse each list until max_depth is reached */ +int RMF_buildTable(FL2_matchTable* const tbl, + size_t const job, + unsigned const multi_thread, + FL2_dataBlock const block) +{ + DEBUGLOG(5, "RMF_buildTable : thread %u", (U32)job); + + if (tbl->is_struct) + RMF_structuredBuildTable(tbl, job, multi_thread, block); + else + RMF_bitpackBuildTable(tbl, job, multi_thread, block); + + if (job == 0 && tbl->st_index >= RADIX_CANCEL_INDEX) { + RMF_initListHeads(tbl); + return 1; + } + return 0; +} + +void RMF_cancelBuild(FL2_matchTable * const tbl) +{ + if(tbl != NULL) + FL2_atomic_add(tbl->st_index, RADIX_CANCEL_INDEX - ATOMIC_INITIAL_VALUE); +} + +void RMF_resetIncompleteBuild(FL2_matchTable * const tbl) +{ + RMF_initListHeads(tbl); +} + +int RMF_integrityCheck(const FL2_matchTable* const tbl, const BYTE* const data, size_t const pos, size_t const end, unsigned const max_depth) +{ + if (tbl->is_struct) + return RMF_structuredIntegrityCheck(tbl, data, pos, end, max_depth); + else + return RMF_bitpackIntegrityCheck(tbl, data, pos, end, max_depth); +} + +void RMF_limitLengths(FL2_matchTable* const tbl, size_t const pos) +{ + if (tbl->is_struct) + RMF_structuredLimitLengths(tbl, pos); + else + RMF_bitpackLimitLengths(tbl, pos); +} + +BYTE* RMF_getTableAsOutputBuffer(FL2_matchTable* const tbl, size_t const pos) +{ + if (tbl->is_struct) + return RMF_structuredAsOutputBuffer(tbl, pos); + else + return RMF_bitpackAsOutputBuffer(tbl, pos); +} + +size_t RMF_memoryUsage(size_t const dict_size, unsigned const buffer_resize, unsigned const thread_count) +{ + size_t size = (size_t)(4U + RMF_isStruct(dict_size)) * dict_size; + size_t const buf_size = RMF_calBufSize(dict_size, buffer_resize); + size += ((buf_size - 1) * sizeof(RMF_buildMatch) + sizeof(RMF_builder)) * thread_count; + return size; +} diff --git a/builtins/flzma2/radix_mf.h b/builtins/flzma2/radix_mf.h new file mode 100644 index 0000000000000..f6ec88c1e3816 --- /dev/null +++ b/builtins/flzma2/radix_mf.h @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#ifndef RADIX_MF_H +#define RADIX_MF_H + +#include "fast-lzma2.h" +#include "data_block.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct FL2_matchTable_s FL2_matchTable; + +#define OVERLAP_FROM_DICT_SIZE(d, o) (((d) >> 4) * (o)) + +#define RMF_MIN_BYTES_PER_THREAD 1024 + +typedef struct +{ + size_t dictionary_size; + unsigned match_buffer_resize; + unsigned overlap_fraction; + unsigned divide_and_conquer; + unsigned depth; +#ifdef RMF_REFERENCE + unsigned use_ref_mf; +#endif +} RMF_parameters; + +FL2_matchTable* RMF_createMatchTable(const RMF_parameters* const params, size_t const dict_reduce, unsigned const thread_count); +void RMF_freeMatchTable(FL2_matchTable* const tbl); +BYTE RMF_compatibleParameters(const FL2_matchTable* const tbl, const RMF_parameters* const params, size_t const dict_reduce); +size_t RMF_applyParameters(FL2_matchTable* const tbl, const RMF_parameters* const params, size_t const dict_reduce); +size_t RMF_threadCount(const FL2_matchTable * const tbl); +void RMF_initProgress(FL2_matchTable * const tbl); +void RMF_initTable(FL2_matchTable* const tbl, const void* const data, size_t const end); +int RMF_buildTable(FL2_matchTable* const tbl, + size_t const job, + unsigned const multi_thread, + FL2_dataBlock const block); +void RMF_cancelBuild(FL2_matchTable* const tbl); +void RMF_resetIncompleteBuild(FL2_matchTable* const tbl); +int RMF_integrityCheck(const FL2_matchTable* const tbl, const BYTE* const data, size_t const pos, size_t const end, unsigned const max_depth); +void RMF_limitLengths(FL2_matchTable* const tbl, size_t const pos); +BYTE* RMF_getTableAsOutputBuffer(FL2_matchTable* const tbl, size_t const pos); +size_t RMF_memoryUsage(size_t const dict_size, unsigned const buffer_resize, unsigned const thread_count); + +#if defined (__cplusplus) +} +#endif + +#endif /* RADIX_MF_H */ \ No newline at end of file diff --git a/builtins/flzma2/radix_struct.c b/builtins/flzma2/radix_struct.c new file mode 100644 index 0000000000000..2117fa0c9e1c9 --- /dev/null +++ b/builtins/flzma2/radix_struct.c @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2018, Conor McCarthy +* All rights reserved. +* +* This source code is licensed under both the BSD-style license (found in the +* LICENSE file in the root directory of this source tree) and the GPLv2 (found +* in the COPYING file in the root directory of this source tree). +* You may select, at your option, one of the above-listed licenses. +*/ + +#include "mem.h" /* U32, U64 */ +#include "fl2_threading.h" +#include "fl2_internal.h" +#include "radix_internal.h" + +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +#define RMF_STRUCTURED + +#define RADIX_MAX_LENGTH STRUCTURED_MAX_LENGTH + +#define InitMatchLink(pos, link) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] = (U32)(link) + +#define GetMatchLink(pos) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] + +#define GetInitialMatchLink(pos) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] + +#define GetMatchLength(pos) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].lengths[(pos) & UNIT_MASK] + +#define SetMatchLink(pos, link, length) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] = (U32)(link) + +#define SetMatchLength(pos, link, length) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].lengths[(pos) & UNIT_MASK] = (BYTE)(length) + +#define SetMatchLinkAndLength(pos, link, length) do { size_t i_ = (pos) >> UNIT_BITS, u_ = (pos) & UNIT_MASK; ((RMF_unit*)tbl->table)[i_].links[u_] = (U32)(link); ((RMF_unit*)tbl->table)[i_].lengths[u_] = (BYTE)(length); } while(0) + +#define SetNull(pos) ((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] = RADIX_NULL_LINK + +#define IsNull(pos) (((RMF_unit*)tbl->table)[(pos) >> UNIT_BITS].links[(pos) & UNIT_MASK] == RADIX_NULL_LINK) + +BYTE* RMF_structuredAsOutputBuffer(FL2_matchTable* const tbl, size_t const pos) +{ + return (BYTE*)((RMF_unit*)tbl->table + (pos >> UNIT_BITS) + ((pos & UNIT_MASK) != 0)); +} + +/* Restrict the match lengths so that they don't reach beyond pos */ +void RMF_structuredLimitLengths(FL2_matchTable* const tbl, size_t const pos) +{ + DEBUGLOG(5, "RMF_limitLengths : end %u, max length %u", (U32)pos, RADIX_MAX_LENGTH); + SetNull(pos - 1); + for (size_t length = 2; length < RADIX_MAX_LENGTH && length <= pos; ++length) { + size_t const i = (pos - length) >> UNIT_BITS; + size_t const u = (pos - length) & UNIT_MASK; + if (((RMF_unit*)tbl->table)[i].links[u] != RADIX_NULL_LINK) { + ((RMF_unit*)tbl->table)[i].lengths[u] = MIN((BYTE)length, ((RMF_unit*)tbl->table)[i].lengths[u]); + } + } +} + +#include "radix_engine.h" \ No newline at end of file diff --git a/builtins/flzma2/range_enc.c b/builtins/flzma2/range_enc.c new file mode 100644 index 0000000000000..313977038ab1a --- /dev/null +++ b/builtins/flzma2/range_enc.c @@ -0,0 +1,211 @@ +/* +* Bitwise range encoder by Igor Pavlov +* Modified by Conor McCarthy +* +* Public domain +*/ + +#include "fl2_internal.h" +#include "mem.h" +#include "platform.h" +#include "range_enc.h" + +/* The first and last elements of these tables are never used */ +BYTE price_table[2][kPriceTableSize] = { { + 0, 193, 182, 166, 154, 145, 137, 131, + 125, 120, 115, 111, 107, 103, 100, 97, + 94, 91, 89, 86, 84, 82, 80, 78, + 76, 74, 72, 71, 69, 67, 66, 64, + 63, 61, 60, 59, 57, 56, 55, 54, + 53, 52, 50, 49, 48, 47, 46, 45, + 44, 43, 42, 42, 41, 40, 39, 38, + 37, 36, 36, 35, 34, 33, 33, 32, + 31, 30, 30, 29, 28, 28, 27, 26, + 26, 25, 25, 24, 23, 23, 22, 21, + 21, 20, 20, 19, 19, 18, 18, 17, + 17, 16, 16, 15, 15, 14, 14, 13, + 13, 12, 12, 11, 11, 10, 10, 9, + 9, 8, 8, 8, 7, 7, 6, 6, + 5, 5, 5, 4, 4, 3, 3, 3, + 2, 2, 2, 1, 1, 0, 0, 0 +}, { + 0, 0, 0, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 5, 5, 5, + 6, 6, 7, 7, 8, 8, 8, 9, + 9, 10, 10, 11, 11, 12, 12, 13, + 13, 13, 14, 14, 15, 15, 16, 17, + 17, 18, 18, 19, 19, 20, 20, 21, + 21, 22, 23, 23, 24, 24, 25, 26, + 26, 27, 28, 28, 29, 30, 30, 31, + 32, 33, 33, 34, 35, 36, 36, 37, + 38, 39, 40, 41, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 53, + 54, 55, 56, 57, 59, 60, 61, 63, + 64, 66, 67, 69, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 89, 91, 94, + 97, 100, 103, 107, 111, 115, 119, 125, + 130, 137, 145, 154, 165, 181, 192, 0 +} }; + +#if 0 + +#include + +/* Generates price_table */ +void RC_printPriceTable() +{ + static const unsigned test_size = 0x4000; + const unsigned test_div = test_size >> 8; + BYTE buf[0x3062]; + unsigned table0[kPriceTableSize]; + unsigned table1[kPriceTableSize]; + unsigned count[kPriceTableSize]; + memset(table0, 0, sizeof(table0)); + memset(table1, 0, sizeof(table1)); + memset(count, 0, sizeof(count)); + for (LZMA2_prob i = 31; i <= kBitModelTotal - 31; ++i) { + RC_encoder rc; + RC_reset(&rc); + RC_setOutputBuffer(&rc, buf); + for (unsigned j = 0; j < test_size; ++j) { + LZMA2_prob prob = i; + RC_encodeBit0(&rc, &prob); + } + RC_flush(&rc); + table0[i >> kNumMoveReducingBits] += (unsigned)rc.out_index - 5; + RC_reset(&rc); + RC_setOutputBuffer(&rc, buf); + for (unsigned j = 0; j < test_size; ++j) { + LZMA2_prob prob = i; + RC_encodeBit1(&rc, &prob); + } + RC_flush(&rc); + table1[i >> kNumMoveReducingBits] += (unsigned)rc.out_index - 5; + ++count[i >> kNumMoveReducingBits]; + } + for (int i = 0; i < kPriceTableSize; ++i) if (count[i]) { + table0[i] = (table0[i] / count[i]) / test_div; + table1[i] = (table1[i] / count[i]) / test_div; + } + fputs("const BYTE price_table[2][kPriceTableSize] = {\r\n", stdout); + for (int i = 0; i < kPriceTableSize;) { + for (int j = 0; j < 8; ++j, ++i) + printf("%4d,", table0[i]); + fputs("\r\n", stdout); + } + fputs("}, {\r\n", stdout); + for (int i = 0; i < kPriceTableSize;) { + for (int j = 0; j < 8; ++j, ++i) + printf("%4d,", table1[i]); + fputs("\r\n", stdout); + } + fputs("} };\r\n", stdout); +} + +#endif + +void RC_setOutputBuffer(RC_encoder* const rc, BYTE *const out_buffer) +{ + rc->out_buffer = out_buffer; + rc->out_index = 0; +} + +void RC_reset(RC_encoder* const rc) +{ + rc->low = 0; + rc->range = (U32)-1; + rc->cache_size = 0; + rc->cache = 0; +} + +#ifdef __64BIT__ + +void FORCE_NOINLINE RC_shiftLow(RC_encoder* const rc) +{ + U64 low = rc->low; + rc->low = (U32)(low << 8); + /* VC15 compiles 'if (low < 0xFF000000 || low > 0xFFFFFFFF)' to this single-branch conditional */ + if (low + 0xFFFFFFFF01000000 > 0xFFFFFF) { + BYTE high = (BYTE)(low >> 32); + rc->out_buffer[rc->out_index++] = rc->cache + high; + rc->cache = (BYTE)(low >> 24); + if (rc->cache_size != 0) { + high += 0xFF; + do { + rc->out_buffer[rc->out_index++] = high; + } while (--rc->cache_size != 0); + } + } + else { + rc->cache_size++; + } +} + +#else + +void FORCE_NOINLINE RC_shiftLow(RC_encoder* const rc) +{ + U32 low = (U32)rc->low; + unsigned high = (unsigned)(rc->low >> 32); + rc->low = low << 8; + if (low < (U32)0xFF000000 || high != 0) { + rc->out_buffer[rc->out_index++] = rc->cache + (BYTE)high; + rc->cache = (BYTE)(low >> 24); + if (rc->cache_size != 0) { + high += 0xFF; + do { + rc->out_buffer[rc->out_index++] = (BYTE)high; + } while (--rc->cache_size != 0); + } + } + else { + rc->cache_size++; + } +} + +#endif + +void RC_encodeBitTree(RC_encoder* const rc, LZMA2_prob *const probs, unsigned bit_count, unsigned symbol) +{ + assert(bit_count > 1); + --bit_count; + unsigned bit = symbol >> bit_count; + RC_encodeBit(rc, &probs[1], bit); + size_t tree_index = 1; + do { + --bit_count; + tree_index = (tree_index << 1) | bit; + bit = (symbol >> bit_count) & 1; + RC_encodeBit(rc, &probs[tree_index], bit); + } while (bit_count != 0); +} + +void RC_encodeBitTreeReverse(RC_encoder* const rc, LZMA2_prob *const probs, unsigned bit_count, unsigned symbol) +{ + assert(bit_count != 0); + unsigned bit = symbol & 1; + RC_encodeBit(rc, &probs[1], bit); + unsigned tree_index = 1; + while (--bit_count != 0) { + tree_index = (tree_index << 1) + bit; + symbol >>= 1; + bit = symbol & 1; + RC_encodeBit(rc, &probs[tree_index], bit); + } +} + +void FORCE_NOINLINE RC_encodeDirect(RC_encoder* const rc, unsigned value, unsigned bit_count) +{ + assert(bit_count > 0); + do { + rc->range >>= 1; + --bit_count; + rc->low += rc->range & -((int)(value >> bit_count) & 1); + if (rc->range < kTopValue) { + rc->range <<= 8; + RC_shiftLow(rc); + } + } while (bit_count != 0); +} + + diff --git a/builtins/flzma2/range_enc.h b/builtins/flzma2/range_enc.h new file mode 100644 index 0000000000000..1d9a50d99f36e --- /dev/null +++ b/builtins/flzma2/range_enc.h @@ -0,0 +1,162 @@ +/* +* Bitwise range encoder by Igor Pavlov +* Modified by Conor McCarthy +* +* Public domain +*/ + +#ifndef RANGE_ENCODER_H +#define RANGE_ENCODER_H + +#include "mem.h" +#include "compiler.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#ifdef LZMA_ENC_PROB32 +typedef U32 LZMA2_prob; +#else +typedef U16 LZMA2_prob; +#endif + +#define kNumTopBits 24U +#define kTopValue (1UL << kNumTopBits) +#define kNumBitModelTotalBits 11U +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5U +#define kProbInitValue (kBitModelTotal >> 1U) +#define kNumMoveReducingBits 4U +#define kNumBitPriceShiftBits 5U +#define kPriceTableSize (kBitModelTotal >> kNumMoveReducingBits) + +extern BYTE price_table[2][kPriceTableSize]; +#if 0 +void RC_printPriceTable(); +#endif + +typedef struct +{ + BYTE *out_buffer; + size_t out_index; + U64 cache_size; + U64 low; + U32 range; + BYTE cache; +} RC_encoder; + +void RC_reset(RC_encoder* const rc); + +void RC_setOutputBuffer(RC_encoder* const rc, BYTE *const out_buffer); + +void FORCE_NOINLINE RC_shiftLow(RC_encoder* const rc); + +void RC_encodeBitTree(RC_encoder* const rc, LZMA2_prob *const probs, unsigned bit_count, unsigned symbol); + +void RC_encodeBitTreeReverse(RC_encoder* const rc, LZMA2_prob *const probs, unsigned bit_count, unsigned symbol); + +void FORCE_NOINLINE RC_encodeDirect(RC_encoder* const rc, unsigned value, unsigned bit_count); + +HINT_INLINE +void RC_encodeBit0(RC_encoder* const rc, LZMA2_prob *const rprob) +{ + unsigned prob = *rprob; + rc->range = (rc->range >> kNumBitModelTotalBits) * prob; + prob += (kBitModelTotal - prob) >> kNumMoveBits; + *rprob = (LZMA2_prob)prob; + if (rc->range < kTopValue) { + rc->range <<= 8; + RC_shiftLow(rc); + } +} + +HINT_INLINE +void RC_encodeBit1(RC_encoder* const rc, LZMA2_prob *const rprob) +{ + unsigned prob = *rprob; + U32 new_bound = (rc->range >> kNumBitModelTotalBits) * prob; + rc->low += new_bound; + rc->range -= new_bound; + prob -= prob >> kNumMoveBits; + *rprob = (LZMA2_prob)prob; + if (rc->range < kTopValue) { + rc->range <<= 8; + RC_shiftLow(rc); + } +} + +HINT_INLINE +void RC_encodeBit(RC_encoder* const rc, LZMA2_prob *const rprob, unsigned const bit) +{ + unsigned prob = *rprob; + if (bit != 0) { + U32 const new_bound = (rc->range >> kNumBitModelTotalBits) * prob; + rc->low += new_bound; + rc->range -= new_bound; + prob -= prob >> kNumMoveBits; + } + else { + rc->range = (rc->range >> kNumBitModelTotalBits) * prob; + prob += (kBitModelTotal - prob) >> kNumMoveBits; + } + *rprob = (LZMA2_prob)prob; + if (rc->range < kTopValue) { + rc->range <<= 8; + RC_shiftLow(rc); + } +} + +#define GET_PRICE(prob, symbol) \ + price_table[symbol][(prob) >> kNumMoveReducingBits] + +#define GET_PRICE_0(prob) price_table[0][(prob) >> kNumMoveReducingBits] + +#define GET_PRICE_1(prob) price_table[1][(prob) >> kNumMoveReducingBits] + +#define kMinLitPrice 8U + +HINT_INLINE +unsigned RC_getTreePrice(const LZMA2_prob* const prob_table, unsigned bit_count, size_t symbol) +{ + unsigned price = 0; + symbol |= ((size_t)1 << bit_count); + do { + size_t const next_symbol = symbol >> 1; + unsigned prob = prob_table[next_symbol]; + size_t bit = symbol & 1; + price += GET_PRICE(prob, bit); + symbol = next_symbol; + } while (symbol != 1); + return price; +} + +HINT_INLINE +unsigned RC_getReverseTreePrice(const LZMA2_prob* const prob_table, unsigned bit_count, size_t symbol) +{ + unsigned prob = prob_table[1]; + size_t bit = symbol & 1; + unsigned price = GET_PRICE(prob, bit); + size_t m = 1; + while (--bit_count != 0) { + m = (m << 1) | bit; + symbol >>= 1; + prob = prob_table[m]; + bit = symbol & 1; + price += GET_PRICE(prob, bit); + } + return price; +} + +HINT_INLINE +void RC_flush(RC_encoder* const rc) +{ + for (int i = 0; i < 5; ++i) + RC_shiftLow(rc); +} + +#if defined (__cplusplus) +} +#endif + +#endif /* RANGE_ENCODER_H */ \ No newline at end of file diff --git a/builtins/flzma2/util.c b/builtins/flzma2/util.c new file mode 100644 index 0000000000000..d6466063594b2 --- /dev/null +++ b/builtins/flzma2/util.c @@ -0,0 +1,707 @@ +/* + * Copyright (c) 2016-present, Przemyslaw Skibinski, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#if defined (__cplusplus) +extern "C" { +#endif + + +/*-**************************************** +* Dependencies +******************************************/ +#include "util.h" /* note : ensure that platform.h is included first ! */ +#include +#include + + +int UTIL_fileExist(const char* filename) +{ + stat_t statbuf; +#if defined(_MSC_VER) + int const stat_error = _stat64(filename, &statbuf); +#else + int const stat_error = stat(filename, &statbuf); +#endif + return !stat_error; +} + +int UTIL_isRegularFile(const char* infilename) +{ + stat_t statbuf; + return UTIL_getFileStat(infilename, &statbuf); /* Only need to know whether it is a regular file */ +} + +int UTIL_getFileStat(const char* infilename, stat_t *statbuf) +{ + int r; +#if defined(_MSC_VER) + r = _stat64(infilename, statbuf); + if (r || !(statbuf->st_mode & S_IFREG)) return 0; /* No good... */ +#else + r = stat(infilename, statbuf); + if (r || !S_ISREG(statbuf->st_mode)) return 0; /* No good... */ +#endif + return 1; +} + +int UTIL_setFileStat(const char *filename, stat_t *statbuf) +{ + int res = 0; + struct utimbuf timebuf; + + if (!UTIL_isRegularFile(filename)) + return -1; + + timebuf.actime = time(NULL); + timebuf.modtime = statbuf->st_mtime; + res += utime(filename, &timebuf); /* set access and modification times */ + +#if !defined(_WIN32) + res += chown(filename, statbuf->st_uid, statbuf->st_gid); /* Copy ownership */ +#endif + + res += chmod(filename, statbuf->st_mode & 07777); /* Copy file permissions */ + + errno = 0; + return -res; /* number of errors is returned */ +} + +U32 UTIL_isDirectory(const char* infilename) +{ + int r; + stat_t statbuf; +#if defined(_MSC_VER) + r = _stat64(infilename, &statbuf); + if (!r && (statbuf.st_mode & _S_IFDIR)) return 1; +#else + r = stat(infilename, &statbuf); + if (!r && S_ISDIR(statbuf.st_mode)) return 1; +#endif + return 0; +} + +U32 UTIL_isLink(const char* infilename) +{ +/* macro guards, as defined in : https://linux.die.net/man/2/lstat */ +#ifndef __STRICT_ANSI__ +#if defined(_BSD_SOURCE) \ + || (defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ + || (defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED)) \ + || (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \ + || (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__OpenBSD__) \ + || defined(__FreeBSD__) + int r; + stat_t statbuf; + r = lstat(infilename, &statbuf); + if (!r && S_ISLNK(statbuf.st_mode)) return 1; +#endif +#endif + (void)infilename; + return 0; +} + +U64 UTIL_getFileSize(const char* infilename) +{ + if (!UTIL_isRegularFile(infilename)) return UTIL_FILESIZE_UNKNOWN; + { int r; +#if defined(_MSC_VER) + struct __stat64 statbuf; + r = _stat64(infilename, &statbuf); + if (r || !(statbuf.st_mode & S_IFREG)) return UTIL_FILESIZE_UNKNOWN; +#elif defined(__MINGW32__) && defined (__MSVCRT__) + struct _stati64 statbuf; + r = _stati64(infilename, &statbuf); + if (r || !(statbuf.st_mode & S_IFREG)) return UTIL_FILESIZE_UNKNOWN; +#else + struct stat statbuf; + r = stat(infilename, &statbuf); + if (r || !S_ISREG(statbuf.st_mode)) return UTIL_FILESIZE_UNKNOWN; +#endif + return (U64)statbuf.st_size; + } +} + + +U64 UTIL_getTotalFileSize(const char* const * const fileNamesTable, unsigned nbFiles) +{ + U64 total = 0; + int error = 0; + unsigned n; + for (n=0; n= *bufEnd) { + ptrdiff_t const newListSize = (*bufEnd - *bufStart) + LIST_SIZE_INCREASE; + *bufStart = (char*)UTIL_realloc(*bufStart, newListSize); + if (*bufStart == NULL) { free(path); FindClose(hFile); return 0; } + *bufEnd = *bufStart + newListSize; + } + if (*bufStart + *pos + pathLength < *bufEnd) { + memcpy(*bufStart + *pos, path, pathLength+1 /* include final \0 */); + *pos += pathLength + 1; + nbFiles++; + } + } + free(path); + } while (FindNextFileA(hFile, &cFile)); + + FindClose(hFile); + return nbFiles; +} + +#elif defined(__linux__) || (PLATFORM_POSIX_VERSION >= 200112L) /* opendir, readdir require POSIX.1-2001 */ + +int UTIL_prepareFileList(const char *dirName, char** bufStart, size_t* pos, char** bufEnd, int followLinks) +{ + DIR *dir; + struct dirent *entry; + char* path; + int dirLength, fnameLength, pathLength, nbFiles = 0; + + if (!(dir = opendir(dirName))) { + UTIL_DISPLAYLEVEL(1, "Cannot open directory '%s': %s\n", dirName, strerror(errno)); + return 0; + } + + dirLength = (int)strlen(dirName); + errno = 0; + while ((entry = readdir(dir)) != NULL) { + if (strcmp (entry->d_name, "..") == 0 || + strcmp (entry->d_name, ".") == 0) continue; + fnameLength = (int)strlen(entry->d_name); + path = (char*) malloc(dirLength + fnameLength + 2); + if (!path) { closedir(dir); return 0; } + memcpy(path, dirName, dirLength); + + path[dirLength] = '/'; + memcpy(path+dirLength+1, entry->d_name, fnameLength); + pathLength = dirLength+1+fnameLength; + path[pathLength] = 0; + + if (!followLinks && UTIL_isLink(path)) { + UTIL_DISPLAYLEVEL(2, "Warning : %s is a symbolic link, ignoring\n", path); + continue; + } + + if (UTIL_isDirectory(path)) { + nbFiles += UTIL_prepareFileList(path, bufStart, pos, bufEnd, followLinks); /* Recursively call "UTIL_prepareFileList" with the new path. */ + if (*bufStart == NULL) { free(path); closedir(dir); return 0; } + } else { + if (*bufStart + *pos + pathLength >= *bufEnd) { + ptrdiff_t newListSize = (*bufEnd - *bufStart) + LIST_SIZE_INCREASE; + *bufStart = (char*)UTIL_realloc(*bufStart, newListSize); + *bufEnd = *bufStart + newListSize; + if (*bufStart == NULL) { free(path); closedir(dir); return 0; } + } + if (*bufStart + *pos + pathLength < *bufEnd) { + memcpy(*bufStart + *pos, path, pathLength + 1); /* with final \0 */ + *pos += pathLength + 1; + nbFiles++; + } + } + free(path); + errno = 0; /* clear errno after UTIL_isDirectory, UTIL_prepareFileList */ + } + + if (errno != 0) { + UTIL_DISPLAYLEVEL(1, "readdir(%s) error: %s\n", dirName, strerror(errno)); + free(*bufStart); + *bufStart = NULL; + } + closedir(dir); + return nbFiles; +} + +#else + +int UTIL_prepareFileList(const char *dirName, char** bufStart, size_t* pos, char** bufEnd, int followLinks) +{ + (void)bufStart; (void)bufEnd; (void)pos; (void)followLinks; + UTIL_DISPLAYLEVEL(1, "Directory %s ignored (compiled without _WIN32 or _POSIX_C_SOURCE)\n", dirName); + return 0; +} + +#endif /* #ifdef _WIN32 */ + +/* + * UTIL_createFileList - takes a list of files and directories (params: inputNames, inputNamesNb), scans directories, + * and returns a new list of files (params: return value, allocatedBuffer, allocatedNamesNb). + * After finishing usage of the list the structures should be freed with UTIL_freeFileList(params: return value, allocatedBuffer) + * In case of error UTIL_createFileList returns NULL and UTIL_freeFileList should not be called. + */ +const char** +UTIL_createFileList(const char **inputNames, unsigned inputNamesNb, + char** allocatedBuffer, unsigned* allocatedNamesNb, + int followLinks) +{ + size_t pos; + unsigned i, nbFiles; + char* buf = (char*)malloc(LIST_SIZE_INCREASE); + char* bufend = buf + LIST_SIZE_INCREASE; + const char** fileTable; + + if (!buf) return NULL; + + for (i=0, pos=0, nbFiles=0; i= bufend) { + ptrdiff_t newListSize = (bufend - buf) + LIST_SIZE_INCREASE; + buf = (char*)UTIL_realloc(buf, newListSize); + bufend = buf + newListSize; + if (!buf) return NULL; + } + if (buf + pos + len < bufend) { + memcpy(buf+pos, inputNames[i], len+1); /* with final \0 */ + pos += len + 1; + nbFiles++; + } + } else { + nbFiles += UTIL_prepareFileList(inputNames[i], &buf, &pos, &bufend, followLinks); + if (buf == NULL) return NULL; + } } + + if (nbFiles == 0) { free(buf); return NULL; } + + fileTable = (const char**)malloc((nbFiles+1) * sizeof(const char*)); + if (!fileTable) { free(buf); return NULL; } + + for (i=0, pos=0; i bufend) { free(buf); free((void*)fileTable); return NULL; } + + *allocatedBuffer = buf; + *allocatedNamesNb = nbFiles; + + return fileTable; +} + +/*-**************************************** +* Console log +******************************************/ +int g_utilDisplayLevel; + + +/*-**************************************** +* Time functions +******************************************/ +#if defined(_WIN32) /* Windows */ + +UTIL_time_t UTIL_getTime(void) { UTIL_time_t x; QueryPerformanceCounter(&x); return x; } + +U64 UTIL_getSpanTimeMicro(UTIL_time_t clockStart, UTIL_time_t clockEnd) +{ + static LARGE_INTEGER ticksPerSecond; + static int init = 0; + if (!init) { + if (!QueryPerformanceFrequency(&ticksPerSecond)) + UTIL_DISPLAYLEVEL(1, "ERROR: QueryPerformanceFrequency() failure\n"); + init = 1; + } + return 1000000ULL*(clockEnd.QuadPart - clockStart.QuadPart)/ticksPerSecond.QuadPart; +} + +U64 UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd) +{ + static LARGE_INTEGER ticksPerSecond; + static int init = 0; + if (!init) { + if (!QueryPerformanceFrequency(&ticksPerSecond)) + UTIL_DISPLAYLEVEL(1, "ERROR: QueryPerformanceFrequency() failure\n"); + init = 1; + } + return 1000000000ULL*(clockEnd.QuadPart - clockStart.QuadPart)/ticksPerSecond.QuadPart; +} + +#elif defined(__APPLE__) && defined(__MACH__) + +UTIL_time_t UTIL_getTime(void) { return mach_absolute_time(); } + +U64 UTIL_getSpanTimeMicro(UTIL_time_t clockStart, UTIL_time_t clockEnd) +{ + static mach_timebase_info_data_t rate; + static int init = 0; + if (!init) { + mach_timebase_info(&rate); + init = 1; + } + return (((clockEnd - clockStart) * (U64)rate.numer) / ((U64)rate.denom))/1000ULL; +} + +U64 UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd) +{ + static mach_timebase_info_data_t rate; + static int init = 0; + if (!init) { + mach_timebase_info(&rate); + init = 1; + } + return ((clockEnd - clockStart) * (U64)rate.numer) / ((U64)rate.denom); +} + +#elif (PLATFORM_POSIX_VERSION >= 200112L) \ + && (defined(__UCLIBC__) \ + || (defined(__GLIBC__) \ + && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) \ + || (__GLIBC__ > 2)))) + +UTIL_time_t UTIL_getTime(void) +{ + UTIL_time_t time; + if (clock_gettime(CLOCK_MONOTONIC, &time)) + UTIL_DISPLAYLEVEL(1, "ERROR: Failed to get time\n"); /* we could also exit() */ + return time; +} + +UTIL_time_t UTIL_getSpanTime(UTIL_time_t begin, UTIL_time_t end) +{ + UTIL_time_t diff; + if (end.tv_nsec < begin.tv_nsec) { + diff.tv_sec = (end.tv_sec - 1) - begin.tv_sec; + diff.tv_nsec = (end.tv_nsec + 1000000000ULL) - begin.tv_nsec; + } else { + diff.tv_sec = end.tv_sec - begin.tv_sec; + diff.tv_nsec = end.tv_nsec - begin.tv_nsec; + } + return diff; +} + +U64 UTIL_getSpanTimeMicro(UTIL_time_t begin, UTIL_time_t end) +{ + UTIL_time_t const diff = UTIL_getSpanTime(begin, end); + U64 micro = 0; + micro += 1000000ULL * diff.tv_sec; + micro += diff.tv_nsec / 1000ULL; + return micro; +} + +U64 UTIL_getSpanTimeNano(UTIL_time_t begin, UTIL_time_t end) +{ + UTIL_time_t const diff = UTIL_getSpanTime(begin, end); + U64 nano = 0; + nano += 1000000000ULL * diff.tv_sec; + nano += diff.tv_nsec; + return nano; +} + +#else /* relies on standard C (note : clock_t measurements can be wrong when using multi-threading) */ + +UTIL_time_t UTIL_getTime(void) { return clock(); } +U64 UTIL_getSpanTimeMicro(UTIL_time_t clockStart, UTIL_time_t clockEnd) { return 1000000ULL * (clockEnd - clockStart) / CLOCKS_PER_SEC; } +U64 UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd) { return 1000000000ULL * (clockEnd - clockStart) / CLOCKS_PER_SEC; } + +#endif + +/* returns time span in microseconds */ +U64 UTIL_clockSpanMicro(UTIL_time_t clockStart ) +{ + UTIL_time_t const clockEnd = UTIL_getTime(); + return UTIL_getSpanTimeMicro(clockStart, clockEnd); +} + +/* returns time span in microseconds */ +U64 UTIL_clockSpanNano(UTIL_time_t clockStart ) +{ + UTIL_time_t const clockEnd = UTIL_getTime(); + return UTIL_getSpanTimeNano(clockStart, clockEnd); +} + +void UTIL_waitForNextTick(void) +{ + UTIL_time_t const clockStart = UTIL_getTime(); + UTIL_time_t clockEnd; + do { + clockEnd = UTIL_getTime(); + } while (UTIL_getSpanTimeNano(clockStart, clockEnd) == 0); +} + +/* count the number of physical cores */ +#if defined(_WIN32) || defined(WIN32) + +#include + +typedef BOOL(WINAPI* LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); + +int UTIL_countPhysicalCores(void) +{ + static int numPhysicalCores = 0; + if (numPhysicalCores != 0) return numPhysicalCores; + + { LPFN_GLPI glpi; + BOOL done = FALSE; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; + DWORD returnLength = 0; + size_t byteOffset = 0; + + glpi = (LPFN_GLPI)GetProcAddress(GetModuleHandle(TEXT("kernel32")), + "GetLogicalProcessorInformation"); + + if (glpi == NULL) { + goto failed; + } + + while(!done) { + DWORD rc = glpi(buffer, &returnLength); + if (FALSE == rc) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (buffer) + free(buffer); + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength); + + if (buffer == NULL) { + perror("zstd"); + exit(1); + } + } else { + /* some other error */ + goto failed; + } + } else { + done = TRUE; + } + } + + ptr = buffer; + + while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { + + if (ptr->Relationship == RelationProcessorCore) { + numPhysicalCores++; + } + + ptr++; + byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + } + + free(buffer); + + return numPhysicalCores; + } + +failed: + /* try to fall back on GetSystemInfo */ + { SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + numPhysicalCores = sysinfo.dwNumberOfProcessors; + if (numPhysicalCores == 0) numPhysicalCores = 1; /* just in case */ + } + return numPhysicalCores; +} + +#elif defined(__APPLE__) + +#include + +/* Use apple-provided syscall + * see: man 3 sysctl */ +int UTIL_countPhysicalCores(void) +{ + static S32 numPhysicalCores = 0; /* apple specifies int32_t */ + if (numPhysicalCores != 0) return numPhysicalCores; + + { size_t size = sizeof(S32); + int const ret = sysctlbyname("hw.physicalcpu", &numPhysicalCores, &size, NULL, 0); + if (ret != 0) { + if (errno == ENOENT) { + /* entry not present, fall back on 1 */ + numPhysicalCores = 1; + } else { + perror("zstd: can't get number of physical cpus"); + exit(1); + } + } + + return numPhysicalCores; + } +} + +#elif defined(__linux__) + +/* parse /proc/cpuinfo + * siblings / cpu cores should give hyperthreading ratio + * otherwise fall back on sysconf */ +int UTIL_countPhysicalCores(void) +{ + static int numPhysicalCores = 0; + + if (numPhysicalCores != 0) return numPhysicalCores; + + numPhysicalCores = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (numPhysicalCores == -1) { + /* value not queryable, fall back on 1 */ + return numPhysicalCores = 1; + } + + /* try to determine if there's hyperthreading */ + { FILE* const cpuinfo = fopen("/proc/cpuinfo", "r"); +#define BUF_SIZE 80 + char buff[BUF_SIZE]; + + int siblings = 0; + int cpu_cores = 0; + int ratio = 1; + + if (cpuinfo == NULL) { + /* fall back on the sysconf value */ + return numPhysicalCores; + } + + /* assume the cpu cores/siblings values will be constant across all + * present processors */ + while (!feof(cpuinfo)) { + if (fgets(buff, BUF_SIZE, cpuinfo) != NULL) { + if (strncmp(buff, "siblings", 8) == 0) { + const char* const sep = strchr(buff, ':'); + if (*sep == '\0') { + /* formatting was broken? */ + goto failed; + } + + siblings = atoi(sep + 1); + } + if (strncmp(buff, "cpu cores", 9) == 0) { + const char* const sep = strchr(buff, ':'); + if (*sep == '\0') { + /* formatting was broken? */ + goto failed; + } + + cpu_cores = atoi(sep + 1); + } + } else if (ferror(cpuinfo)) { + /* fall back on the sysconf value */ + goto failed; + } + } + if (siblings && cpu_cores) { + ratio = siblings / cpu_cores; + } +failed: + fclose(cpuinfo); + return numPhysicalCores = numPhysicalCores / ratio; + } +} + +#elif defined(__FreeBSD__) + +#include +#include + +/* Use physical core sysctl when available + * see: man 4 smp, man 3 sysctl */ +int UTIL_countPhysicalCores(void) +{ + static int numPhysicalCores = 0; /* freebsd sysctl is native int sized */ + if (numPhysicalCores != 0) return numPhysicalCores; + +#if __FreeBSD_version >= 1300008 + { size_t size = sizeof(numPhysicalCores); + int ret = sysctlbyname("kern.smp.cores", &numPhysicalCores, &size, NULL, 0); + if (ret == 0) return numPhysicalCores; + if (errno != ENOENT) { + perror("zstd: can't get number of physical cpus"); + exit(1); + } + /* sysctl not present, fall through to older sysconf method */ + } +#endif + + numPhysicalCores = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (numPhysicalCores == -1) { + /* value not queryable, fall back on 1 */ + numPhysicalCores = 1; + } + return numPhysicalCores; +} + +#elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) + +/* Use POSIX sysconf + * see: man 3 sysconf */ +int UTIL_countPhysicalCores(void) +{ + static int numPhysicalCores = 0; + + if (numPhysicalCores != 0) return numPhysicalCores; + + numPhysicalCores = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (numPhysicalCores == -1) { + /* value not queryable, fall back on 1 */ + return numPhysicalCores = 1; + } + return numPhysicalCores; +} + +#else + +int UTIL_countPhysicalCores(void) +{ + /* assume 1 */ + return 1; +} + +#endif + +#if defined (__cplusplus) +} +#endif diff --git a/builtins/flzma2/util.h b/builtins/flzma2/util.h new file mode 100644 index 0000000000000..f78bcbe1b3f8c --- /dev/null +++ b/builtins/flzma2/util.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2016-present, Przemyslaw Skibinski, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef UTIL_H_MODULE +#define UTIL_H_MODULE + +#if defined (__cplusplus) +extern "C" { +#endif + + +/*-**************************************** +* Dependencies +******************************************/ +#include "platform.h" /* PLATFORM_POSIX_VERSION, ZSTD_NANOSLEEP_SUPPORT, ZSTD_SETPRIORITY_SUPPORT */ +#include /* malloc, realloc, free */ +#include /* size_t, ptrdiff_t */ +#include /* fprintf */ +#include /* stat, utime */ +#include /* stat, chmod */ +#if defined(_MSC_VER) +# include /* utime */ +# include /* _chmod */ +#else +# include /* chown, stat */ +# include /* utime */ +#endif +#include /* clock_t, clock, CLOCKS_PER_SEC, nanosleep */ +#include "mem.h" /* U32, U64 */ + + +/*-************************************************************ +* Avoid fseek()'s 2GiB barrier with MSVC, macOS, *BSD, MinGW +***************************************************************/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +# define UTIL_fseek _fseeki64 +#elif !defined(__64BIT__) && (PLATFORM_POSIX_VERSION >= 200112L) /* No point defining Large file for 64 bit */ +# define UTIL_fseek fseeko +#elif defined(__MINGW32__) && defined(__MSVCRT__) && !defined(__STRICT_ANSI__) && !defined(__NO_MINGW_LFS) +# define UTIL_fseek fseeko64 +#else +# define UTIL_fseek fseek +#endif + + +/*-************************************************* +* Sleep & priority functions: Windows - Posix - others +***************************************************/ +#if defined(_WIN32) +# include +# define SET_REALTIME_PRIORITY SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) +# define UTIL_sleep(s) Sleep(1000*s) +# define UTIL_sleepMilli(milli) Sleep(milli) + +#elif PLATFORM_POSIX_VERSION > 0 /* Unix-like operating system */ +# include /* sleep */ +# define UTIL_sleep(s) sleep(s) +# if ZSTD_NANOSLEEP_SUPPORT /* necessarily defined in platform.h */ +# define UTIL_sleepMilli(milli) { struct timespec t; t.tv_sec=0; t.tv_nsec=milli*1000000ULL; nanosleep(&t, NULL); } +# else +# define UTIL_sleepMilli(milli) /* disabled */ +# endif +# if ZSTD_SETPRIORITY_SUPPORT +# include /* setpriority */ +# define SET_REALTIME_PRIORITY setpriority(PRIO_PROCESS, 0, -20) +# else +# define SET_REALTIME_PRIORITY /* disabled */ +# endif + +#else /* unknown non-unix operating systen */ +# define UTIL_sleep(s) /* disabled */ +# define UTIL_sleepMilli(milli) /* disabled */ +# define SET_REALTIME_PRIORITY /* disabled */ +#endif + + +/*-************************************* +* Constants +***************************************/ +#define LIST_SIZE_INCREASE (8*1024) + + +/*-**************************************** +* Compiler specifics +******************************************/ +#if defined(__INTEL_COMPILER) +# pragma warning(disable : 177) /* disable: message #177: function was declared but never referenced, useful with UTIL_STATIC */ +#endif +#if defined(__GNUC__) +# define UTIL_STATIC static __attribute__((unused)) +#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define UTIL_STATIC static inline +#elif defined(_MSC_VER) +# define UTIL_STATIC static __inline +#else +# define UTIL_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ +#endif + + +/*-**************************************** +* Console log +******************************************/ +extern int g_utilDisplayLevel; +#define UTIL_DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define UTIL_DISPLAYLEVEL(l, ...) { if (g_utilDisplayLevel>=l) { UTIL_DISPLAY(__VA_ARGS__); } } + + +/*-**************************************** +* Time functions +******************************************/ +#if defined(_WIN32) /* Windows */ + + #define UTIL_TIME_INITIALIZER { { 0, 0 } } + typedef LARGE_INTEGER UTIL_time_t; + +#elif defined(__APPLE__) && defined(__MACH__) + + #include + #define UTIL_TIME_INITIALIZER 0 + typedef U64 UTIL_time_t; + +#elif (PLATFORM_POSIX_VERSION >= 200112L) \ + && (defined(__UCLIBC__) \ + || (defined(__GLIBC__) \ + && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) \ + || (__GLIBC__ > 2)))) + + #define UTIL_TIME_INITIALIZER { 0, 0 } + typedef struct timespec UTIL_freq_t; + typedef struct timespec UTIL_time_t; + + UTIL_time_t UTIL_getSpanTime(UTIL_time_t begin, UTIL_time_t end); + +#else /* relies on standard C (note : clock_t measurements can be wrong when using multi-threading) */ + + typedef clock_t UTIL_time_t; + #define UTIL_TIME_INITIALIZER 0 + +#endif + +UTIL_time_t UTIL_getTime(void); +U64 UTIL_getSpanTimeMicro(UTIL_time_t clockStart, UTIL_time_t clockEnd); +U64 UTIL_getSpanTimeNano(UTIL_time_t clockStart, UTIL_time_t clockEnd); + +#define SEC_TO_MICRO 1000000 + +/* returns time span in microseconds */ +U64 UTIL_clockSpanMicro(UTIL_time_t clockStart); + +/* returns time span in microseconds */ +U64 UTIL_clockSpanNano(UTIL_time_t clockStart); +void UTIL_waitForNextTick(void); + +/*-**************************************** +* File functions +******************************************/ +#if defined(_MSC_VER) + #define chmod _chmod + typedef struct __stat64 stat_t; +#else + typedef struct stat stat_t; +#endif + + +int UTIL_fileExist(const char* filename); +int UTIL_isRegularFile(const char* infilename); +int UTIL_setFileStat(const char* filename, stat_t* statbuf); +U32 UTIL_isDirectory(const char* infilename); +int UTIL_getFileStat(const char* infilename, stat_t* statbuf); + +U32 UTIL_isLink(const char* infilename); +#define UTIL_FILESIZE_UNKNOWN ((U64)(-1)) +U64 UTIL_getFileSize(const char* infilename); + +U64 UTIL_getTotalFileSize(const char* const * const fileNamesTable, unsigned nbFiles); + +/* + * A modified version of realloc(). + * If UTIL_realloc() fails the original block is freed. +*/ +UTIL_STATIC void* UTIL_realloc(void *ptr, size_t size) +{ + void *newptr = realloc(ptr, size); + if (newptr) return newptr; + free(ptr); + return NULL; +} + +int UTIL_prepareFileList(const char* dirName, char** bufStart, size_t* pos, char** bufEnd, int followLinks); +#ifdef _WIN32 +# define UTIL_HAS_CREATEFILELIST +#elif defined(__linux__) || (PLATFORM_POSIX_VERSION >= 200112L) /* opendir, readdir require POSIX.1-2001 */ +# define UTIL_HAS_CREATEFILELIST +# include /* opendir, readdir */ +# include /* strerror, memcpy */ +#else +#endif /* #ifdef _WIN32 */ + +/* + * UTIL_createFileList - takes a list of files and directories (params: inputNames, inputNamesNb), scans directories, + * and returns a new list of files (params: return value, allocatedBuffer, allocatedNamesNb). + * After finishing usage of the list the structures should be freed with UTIL_freeFileList(params: return value, allocatedBuffer) + * In case of error UTIL_createFileList returns NULL and UTIL_freeFileList should not be called. + */ +const char** +UTIL_createFileList(const char **inputNames, unsigned inputNamesNb, + char** allocatedBuffer, unsigned* allocatedNamesNb, + int followLinks); + +UTIL_STATIC void UTIL_freeFileList(const char** filenameTable, char* allocatedBuffer) +{ + if (allocatedBuffer) free(allocatedBuffer); + if (filenameTable) free((void*)filenameTable); +} + +int UTIL_countPhysicalCores(void); + +#if defined (__cplusplus) +} +#endif + +#endif /* UTIL_H_MODULE */ diff --git a/cmake/modules/FindFLZMA2.cmake b/cmake/modules/FindFLZMA2.cmake new file mode 100644 index 0000000000000..e3af3b16a6c5a --- /dev/null +++ b/cmake/modules/FindFLZMA2.cmake @@ -0,0 +1,70 @@ +# Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +#.rst: +# FindFLMA2 +# ------- +# +# Find the FLMA2 library header and define variables. +# +# Imported Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines :prop_tgt:`IMPORTED` target ``FLMA2::FLMA2``, +# if FLMA2 has been found +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following variables: +# +# :: +# +# FLMA2_FOUND - True if FLMA2 is found. +# FLMA2_INCLUDE_DIRS - Where to find fast-lzma2.h +# +# :: +# +# FLMA2_VERSION - The version of FLMA2 found (x.y.z) +# FLMA2_VERSION_MAJOR - The major version of FLMA2 +# FLMA2_VERSION_MINOR - The minor version of FLMA2 +# FLMA2_VERSION_PATCH - The patch version of FLMA2 + +find_path(FLMA2_INCLUDE_DIR NAME fast-lzma2.h PATH_SUFFIXES include) + +if(NOT FLMA2_LIBRARY) + find_library(FLMA2_LIBRARY NAMES fast-lzma2 PATH_SUFFIXES lib) +endif() + +mark_as_advanced(FLMA2_INCLUDE_DIR) + +if(FLMA2_INCLUDE_DIR AND EXISTS "${FLMA2_INCLUDE_DIR}/fast-lzma2.h") + file(STRINGS "fast-lzma2.h" FLMA2_H REGEX "^#define FL2_VERSION_[A-Z]+[ ]+[0-9]+.*$") + string(REGEX REPLACE ".+FLMA2_VERSION_MAJOR[ ]+([0-9]+).*$" "\\1" FL2_VERSION_MAJOR "${FLMA2_H}") + string(REGEX REPLACE ".+FLMA2_VERSION_MINOR[ ]+([0-9]+).*$" "\\1" FL2_VERSION_MINOR "${FLMA2_H}") + string(REGEX REPLACE ".+FLMA2_VERSION_RELEASE[ ]+([0-9]+).*$" "\\1" FL2_VERSION_PATCH "${FLMA2_H}") + set(FLMA2_VERSION_STRING "${FLMA2_VERSION_MAJOR}.${FLMA2_VERSION_MINOR}.${FLMA2_VERSION_PATCH}") + unset(FLMA2_H) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FLMA2 + REQUIRED_VARS FLMA2_LIBRARY FLMA2_INCLUDE_DIR VERSION_VAR FLMA2_VERSION) + +if(FLMA2_FOUND) + set(FLMA2_INCLUDE_DIRS "${FLMA2_INCLUDE_DIR}") + + if(NOT FLMA2_LIBRARIES) + set(FLMA2_LIBRARIES ${FLMA2_LIBRARY}) + endif() + + if(NOT TARGET FLMA2::FLMA2) + add_library(FLMA2::FLMA2 UNKNOWN IMPORTED) + set_target_properties(FLMA2::FLMA2 PROPERTIES + IMPORTED_LOCATION "${FLMA2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FLMA2_INCLUDE_DIRS}") + endif() +endif() diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake index be31bcea09a02..c83943e7bde05 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -93,6 +93,8 @@ ROOT_BUILD_OPTION(builtin_glew OFF "Build bundled copy of GLEW") ROOT_BUILD_OPTION(builtin_gsl OFF "Build GSL internally (requires network)") ROOT_BUILD_OPTION(builtin_llvm ON "Build bundled copy of LLVM") ROOT_BUILD_OPTION(builtin_lz4 OFF "Build bundled copy of lz4") +# We need to have it currently as a required dependency +ROOT_BUILD_OPTION(builtin_flzma2 ON "Build bundled copy of flzma2") ROOT_BUILD_OPTION(builtin_lzma OFF "Build bundled copy of lzma") ROOT_BUILD_OPTION(builtin_nlohmannjson ON "Use nlohmann/json.hpp file distributed with ROOT") ROOT_BUILD_OPTION(builtin_openssl OFF "Build OpenSSL internally (requires network)") @@ -204,13 +206,13 @@ if(all AND minimal) endif() #--- Compression algorithms in ROOT------------------------------------------------------------- -set(compression_default "zlib" CACHE STRING "Default compression algorithm (zlib (default), lz4, zstd or lzma)") +set(compression_default "zlib" CACHE STRING "Default compression algorithm (zlib (default), lz4, zstd, flzma2 or lzma)") string(TOLOWER "${compression_default}" compression_default) -if("${compression_default}" MATCHES "zlib|lz4|lzma|zstd") +if("${compression_default}" MATCHES "zlib|lz4|lzma|flzma2|zstd") message(STATUS "ROOT default compression algorithm: ${compression_default}") else() message(FATAL_ERROR "Unsupported compression algorithm: ${compression_default}\n" - "Known values are zlib, lzma, lz4, zstd (case-insensitive).") + "Known values are zlib, lzma, flzma2, lz4, zstd (case-insensitive).") endif() #--- The 'all' option swithes ON major options--------------------------------------------------- @@ -288,6 +290,7 @@ if(builtin_all) set(builtin_llvm_defvalue ON) set(builtin_lz4_defvalue ON) set(builtin_lzma_defvalue ON) + set(builtin_flzma2_defvalue ON) set(builtin_nlohmannjson_defvalue ON) set(builtin_openssl_defvalue ON) set(builtin_openui5_defvalue ON) diff --git a/cmake/modules/SearchInstalledSoftware.cmake b/cmake/modules/SearchInstalledSoftware.cmake index 1778dba165659..bc2a8992c02af 100644 --- a/cmake/modules/SearchInstalledSoftware.cmake +++ b/cmake/modules/SearchInstalledSoftware.cmake @@ -298,6 +298,29 @@ if(builtin_zstd) add_subdirectory(builtins/zstd) endif() +#---Check for FLZMA2------------------------------------------------------------------- +if(NOT builtin_flzma2) + message(STATUS "Looking for FLZMA2") + foreach(suffix FOUND INCLUDE_DIR LIBRARY LIBRARIES LIBRARY_DEBUG LIBRARY_RELEASE) + unset(FLZMA2_${suffix} CACHE) + endforeach() + # As for fail-on-missing, for now it is mandatory package. + # We don't expect you to have it as builtins, so no REQUIRED anymore. + find_package(FLZMA2) + if(NOT FLZMA2_FOUND) + message(STATUS "FLZMA2 not found. Switching on builtin_FLZMA2 option") + set(builtin_flzma2 ON CACHE BOOL "Enabled because FLZMA2 not found (${builtin_flzma2_description})" FORCE) + elseif(FLZMA2_FOUND AND FLZMA2_VERSION VERSION_LESS 1.0.0) + message(STATUS "Version of installed FLZMA2 is too old: ${FLZMA2_VERSION}. Switching on builtin_flzma2 option") + set(builtin_flzma2 ON CACHE BOOL "Enabled because FLZMA2 not found (${builtin_flzma2_description})" FORCE) + endif() +endif() + +if(builtin_flzma2) + list(APPEND ROOT_BUILTINS FLZMA2) + add_subdirectory(builtins/flzma2) +endif() + #---Check for LZ4-------------------------------------------------------------------- if(NOT builtin_lz4) message(STATUS "Looking for LZ4") diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index b7576be87ec40..dc4bc24db8706 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -81,6 +81,7 @@ add_subdirectory(zip) add_subdirectory(lzma) add_subdirectory(lz4) add_subdirectory(zstd) +add_subdirectory(flzma2) if(asan) add_subdirectory(sanitizer) endif() @@ -117,6 +118,7 @@ set(objectlibs $ $ $ $ + $ $ $ $ @@ -261,6 +263,7 @@ target_link_libraries(Core xxHash::xxHash LZ4::LZ4 ZLIB::ZLIB + ${FLMA2_LIBRARIES} ${ZSTD_LIBRARIES} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} diff --git a/core/flzma2/CMakeLists.txt b/core/flzma2/CMakeLists.txt new file mode 100644 index 0000000000000..7e34712968f0c --- /dev/null +++ b/core/flzma2/CMakeLists.txt @@ -0,0 +1,19 @@ +############################################################################ +# CMakeLists.txt file for building ROOT core/lzmafl2 package +############################################################################ + +find_package(FLZMA2 REQUIRED) + +#---Declare ZipLZMAFL2 sources as part of libCore------------------------------- +set(headers ${CMAKE_CURRENT_SOURCE_DIR}/inc/ZipFLZMA2.h) +set(sources ${CMAKE_CURRENT_SOURCE_DIR}/src/ZipFLZMA2.cxx) + +ROOT_OBJECT_LIBRARY(FLzma2 ${sources} BUILTINS FLZMA2) +target_include_directories(FLzma2 PRIVATE + ${FLMA2_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/core/foundation/inc + ${CMAKE_BINARY_DIR}/ginclude +) + +ROOT_INSTALL_HEADERS() +install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/core/flzma2/inc/ZipFLZMA2.h b/core/flzma2/inc/ZipFLZMA2.h new file mode 100644 index 0000000000000..5ad20e20b2dfb --- /dev/null +++ b/core/flzma2/inc/ZipFLZMA2.h @@ -0,0 +1,25 @@ +// Original Author: Oksana Shadura +/************************************************************************* + * Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_ZipLZMAFL2 +#define ROOT_ZipLZMAFL2 + +#ifdef __cplusplus +extern "C" { +#endif + +void R__zipFLZMA2(int cxlevel, int *srcsize, char *src, int *tgtsize, char *tgt, int *irep); + +void R__unzipFLZMA2(int *srcsize, unsigned char *src, int *tgtsize, unsigned char *tgt, int *irep); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/flzma2/src/ZipFLZMA2.cxx b/core/flzma2/src/ZipFLZMA2.cxx new file mode 100644 index 0000000000000..1680d60d40593 --- /dev/null +++ b/core/flzma2/src/ZipFLZMA2.cxx @@ -0,0 +1,69 @@ +// Original Author: Oksana Shadura +/************************************************************************* + * Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +#include "ZipFLZMA2.h" +#include "ROOT/RConfig.hxx" + +#include "fast-lzma2.h" + +#include +#include +#include +#include + +static const int kHeaderSize = 9; + +void R__zipFLZMA2(int cxlevel, int *srcsize, char *src, int *tgtsize, char *tgt, int *irep) +{ + using Ctx_ptr = std::unique_ptr; + Ctx_ptr fCtx{FL2_createCCtx(), &FL2_freeCCtx}; + + *irep = 0; + + size_t retval = FL2_compressCCtx(fCtx.get(), + &tgt[kHeaderSize], static_cast(*tgtsize - kHeaderSize), + src, static_cast(*srcsize), + 2*cxlevel); + + *irep = static_cast(retval + kHeaderSize); + + size_t deflate_size = retval; + size_t inflate_size = static_cast(*srcsize); + tgt[0] = 'X'; + tgt[1] = 'Z'; + tgt[2] = 0; + tgt[3] = deflate_size & 0xff; + tgt[4] = (deflate_size >> 8) & 0xff; + tgt[5] = (deflate_size >> 16) & 0xff; + tgt[6] = inflate_size & 0xff; + tgt[7] = (inflate_size >> 8) & 0xff; + tgt[8] = (inflate_size >> 16) & 0xff; +} + +// We will not use in this test case since files supposely should be decompressed with LZMA: +// src[0] != 'X' || src[1] != 'Z' +void R__unzipFLZMA2(int *srcsize, unsigned char *src, int *tgtsize, unsigned char *tgt, int *irep) +{ + using Ctx_ptr = std::unique_ptr; + Ctx_ptr fCtx{FL2_createDCtx(), &FL2_freeDCtx}; + *irep = 0; + + if (R__unlikely(src[0] != 'X' || src[1] != 'Z' || src[2] == 0)) { + std::cerr << "R__unzipLZMAFL2: algorithm run against buffer with incorrect header (got " << + src[0] << src[1] << src[2] << "; expected XZ)." << std::endl; + return; + } + + size_t retval = FL2_decompressDCtx(fCtx.get(), + (char *)tgt, static_cast(*tgtsize), + (char *)&src[kHeaderSize], static_cast(*srcsize - kHeaderSize)); + + *irep = retval; +} diff --git a/core/zip/CMakeLists.txt b/core/zip/CMakeLists.txt index 03a0c869e1afc..4a737b4d38397 100644 --- a/core/zip/CMakeLists.txt +++ b/core/zip/CMakeLists.txt @@ -18,6 +18,7 @@ ROOT_OBJECT_LIBRARY(Zip target_include_directories(Zip PRIVATE ${ZLIB_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/lzma/inc + ${CMAKE_SOURCE_DIR}/core/flzma2/inc ${CMAKE_SOURCE_DIR}/core/lz4/inc ${CMAKE_SOURCE_DIR}/core/zstd/inc ${CMAKE_SOURCE_DIR}/core/foundation/inc diff --git a/core/zip/inc/Compression.h b/core/zip/inc/Compression.h index d20942d9cf6ab..d31ed49f4abac 100644 --- a/core/zip/inc/Compression.h +++ b/core/zip/inc/Compression.h @@ -75,7 +75,9 @@ struct RCompressionSetting { /// Compression level reserved for old ROOT compression algorithm kDefaultOld = 6, /// Compression level reserved for LZMA compression algorithm (slowest compression with smallest files) - kDefaultLZMA = 7 + kDefaultLZMA = 7, + /// Compression level reserved for FLZMA2 compression algorithm (slowest compression with smallest files) + kDefaultFLZMA2 = 7 }; }; struct EAlgorithm { /// Note: this is only temporarily a struct and will become a enum class hence the name @@ -96,6 +98,8 @@ struct RCompressionSetting { kLZ4, /// Use ZSTD compression kZSTD, + /// Use FLZMA2 compression + kFLZMA2, /// Undefined compression algorithm (must be kept the last of the list in case a new algorithm is added). kUndefined }; @@ -118,6 +122,8 @@ enum ECompressionAlgorithm { /// Deprecated name, do *not* use: kZSTD = RCompressionSetting::EAlgorithm::kZSTD, /// Deprecated name, do *not* use: + kFLZMA2 = RCompressionSetting::EAlgorithm::kFLZMA2, + /// Deprecated name, do *not* use: kUndefinedCompressionAlgorithm = RCompressionSetting::EAlgorithm::kUndefined }; diff --git a/core/zip/src/RZip.cxx b/core/zip/src/RZip.cxx index bdd285494095b..0adedd18dc120 100644 --- a/core/zip/src/RZip.cxx +++ b/core/zip/src/RZip.cxx @@ -11,6 +11,7 @@ #include "RZip.h" #include "Bits.h" #include "ZipLZMA.h" +#include "ZipFLZMA2.h" #include "ZipLZ4.h" #include "ZipZSTD.h" @@ -96,6 +97,8 @@ void R__zipMultipleAlgorithm(int cxlevel, int *srcsize, char *src, int *tgtsize, // The LZMA compression algorithm from the XZ package if (compressionAlgorithm == ROOT::RCompressionSetting::EAlgorithm::kLZMA) { R__zipLZMA(cxlevel, srcsize, src, tgtsize, tgt, irep); + } else if (compressionAlgorithm == ROOT::RCompressionSetting::EAlgorithm::kFLZMA2) { + R__zipFLZMA2(cxlevel, srcsize, src, tgtsize, tgt, irep); } else if (compressionAlgorithm == ROOT::RCompressionSetting::EAlgorithm::kLZ4) { R__zipLZ4(cxlevel, srcsize, src, tgtsize, tgt, irep); } else if (compressionAlgorithm == ROOT::RCompressionSetting::EAlgorithm::kZSTD) { @@ -263,6 +266,11 @@ static int is_valid_header_lzma(unsigned char *src) return src[0] == 'X' && src[1] == 'Z' && src[2] == 0; } +//static int is_valid_header_flzma2(unsigned char *src) +//{ +// return src[0] == 'X' && src[1] == 'Z' && src[2] == 'F'; +//} + static int is_valid_header_lz4(unsigned char *src) { return src[0] == 'L' && src[1] == '4'; @@ -363,6 +371,12 @@ void R__unzip(int *srcsize, uch *src, int *tgtsize, uch *tgt, int *irep) } else if (is_valid_header_lzma(src)) { R__unzipLZMA(srcsize, src, tgtsize, tgt, irep); return; + // We will not use in this test case since files supposely should be decompressed with LZMA: + // (src[0] != 'X' || src[1] != 'Z') + // + //} else if (is_valid_header_flzma2(src)) { + // R__unzipFLZMA2(srcsize, src, tgtsize, tgt, irep); + // return; } else if (is_valid_header_lz4(src)) { R__unzipLZ4(srcsize, src, tgtsize, tgt, irep); return;