diff --git a/CMakeLists.txt b/CMakeLists.txt index 232af968f..6eb434b7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,11 @@ option(LEVELDB_BUILD_TESTS "" OFF) option(LEVELDB_AUTORUN_TESTS "" OFF) option(DBSQL "Enable PostgeSQL server connection" OFF) +# The node binary always carries both DB backends; runtime selection is via +# config.ini `db_backend` (berkeleydb default, rocksdb opt-in). +set(CSDB_BACKEND "both" CACHE INTERNAL "csdb storage backend (locked to 'both')") +message(STATUS "csdb backend: both (locked)") + IF(DBSQL) ADD_DEFINITIONS(-DDBSQL) ENDIF(DBSQL) @@ -88,6 +93,10 @@ endif() # This large list is necessary to prevent thrift from generating unused stuff add_definitions(-DUSE_STD_THREAD) +# Disable Boost's MSVC auto-link pragmas; we link Boost targets explicitly via +# CMake. Without this the linker hunts for libboost_*-vc142-mt-s-x64-*.lib. +add_definitions(-DBOOST_ALL_NO_LIB) + if (MSVC) MARK_AS_ADVANCED( CMAKE_CXX_FLAGS_RELMONITOR diff --git a/client/config/config.cpp b/client/config/config.cpp index f1325669f..90b96ad99 100644 --- a/client/config/config.cpp +++ b/client/config/config.cpp @@ -962,6 +962,11 @@ void Config::readStorageData(const boost::property_tree::ptree& config) { storageData_.checkpointEvery = 1000; } checkAndSaveValue(data, block, PARAM_NAME_STORAGE_CHECKPOINT_EVERY_MINUTES, storageData_.checkpointEveryMinutes); + checkAndSaveValue(data, block, "async_write_queue_size", storageData_.asyncWriteQueueSize); + checkAndSaveValue(data, block, "write_batch_size", storageData_.writeBatchSize); + checkAndSaveValue(data, block, "db_backend", storageData_.dbBackend); + checkAndSaveValue(data, block, "rocksdb_block_cache_mb", storageData_.rocksdbBlockCacheMb); + checkAndSaveValue(data, block, "rocksdb_memtable_mb", storageData_.rocksdbMemtableMb); } void Config::readApiData(const boost::property_tree::ptree& config) { diff --git a/client/config/config.hpp b/client/config/config.hpp index b014d2366..90d155005 100644 --- a/client/config/config.hpp +++ b/client/config/config.hpp @@ -62,6 +62,11 @@ struct StorageData { size_t checkpointKeep = 5; // retained periodic checkpoints (qs/0 always kept on top) size_t checkpointEvery = 500'000; // blocks between periodic checkpoints (rolling history depth = checkpointEvery * checkpointKeep) size_t checkpointEveryMinutes = 0; // wall-clock fallback: also save if this many minutes elapsed since last save (0 = disabled; opt-in for slow networks) + size_t asyncWriteQueueSize = 5000; // bounded queue for the async DB writer + size_t writeBatchSize = 100; // pools coalesced into one DB write + std::string dbBackend = "berkeleydb"; // runtime DB backend (CSDB_BACKEND=both builds); set to "rocksdb" to opt in + size_t rocksdbBlockCacheMb = 1024; // RocksDB shared block cache (MiB); 0 = built-in default + size_t rocksdbMemtableMb = 256; // RocksDB write_buffer_size (MiB); 0 = built-in default }; struct ApiData { diff --git a/csdb/CMakeLists.txt b/csdb/CMakeLists.txt index ca0968ef7..d488d5a4b 100644 --- a/csdb/CMakeLists.txt +++ b/csdb/CMakeLists.txt @@ -10,6 +10,16 @@ option(CSDB_BUILD_BENCHMARK "Bulid benchmark" OFF) include (TestBigEndian) TEST_BIG_ENDIAN(CSDB_PLATFORM_IS_BIG_ENDIAN) +if(CSDB_BACKEND STREQUAL "both") + set(CSDB_BACKEND_SOURCES + src/database_berkeleydb.cpp include/csdb/database_berkeleydb.hpp + src/database_rocksdb.cpp include/csdb/database_rocksdb.hpp) +elseif(CSDB_BACKEND STREQUAL "rocksdb") + set(CSDB_BACKEND_SOURCES src/database_rocksdb.cpp include/csdb/database_rocksdb.hpp) +else() + set(CSDB_BACKEND_SOURCES src/database_berkeleydb.cpp include/csdb/database_berkeleydb.hpp) +endif() + add_library(${PROJECT_NAME} STATIC src/csdb.cpp src/amount.cpp @@ -29,7 +39,7 @@ add_library(${PROJECT_NAME} STATIC src/priv_crypto.cpp src/priv_crypto.hpp src/database.cpp - src/database_berkeleydb.cpp + ${CSDB_BACKEND_SOURCES} src/user_field.cpp include/csdb/internal/shared_data.hpp include/csdb/internal/shared_data_ptr_implementation.hpp @@ -48,7 +58,6 @@ add_library(${PROJECT_NAME} STATIC include/csdb/wallet.hpp include/csdb/storage.hpp include/csdb/database.hpp - include/csdb/database_berkeleydb.hpp include/csdb/user_field.hpp ) @@ -72,13 +81,24 @@ target_include_directories( ) include_directories(${Boost_INCLUDE_DIR}) +set(CSDB_BACKEND_LIBS "") +if(CSDB_BACKEND STREQUAL "both") + list(APPEND CSDB_BACKEND_LIBS BerkeleyDB rocksdb) + target_compile_definitions(${PROJECT_NAME} PRIVATE CSDB_USE_ROCKSDB CSDB_USE_BERKELEYDB) +elseif(CSDB_BACKEND STREQUAL "rocksdb") + list(APPEND CSDB_BACKEND_LIBS rocksdb) + target_compile_definitions(${PROJECT_NAME} PRIVATE CSDB_USE_ROCKSDB) +else() + list(APPEND CSDB_BACKEND_LIBS BerkeleyDB) +endif() + target_link_libraries( ${PROJECT_NAME} cscrypto Boost::system Boost::filesystem Boost::disable_autolinking - BerkeleyDB + ${CSDB_BACKEND_LIBS} lz4 lib ) diff --git a/csdb/include/csdb/database.hpp b/csdb/include/csdb/database.hpp index 498a6b542..c5892a974 100644 --- a/csdb/include/csdb/database.hpp +++ b/csdb/include/csdb/database.hpp @@ -41,6 +41,14 @@ class Database { virtual bool remove(const cs::Bytes& key) = 0; virtual bool seq_no(const cs::Bytes& key, uint32_t* value) = 0; // sequence from block hash + // Vectorized put. Backends may override to coalesce writes (e.g. RocksDB WriteBatch). + struct PendingWrite { + cs::Bytes hash_key; + uint32_t seq_no; + cs::Bytes payload; + }; + virtual bool put_batch(const std::vector& items); + using Item = std::pair; using ItemList = std::vector; virtual bool write_batch(const ItemList& items) = 0; @@ -48,6 +56,9 @@ class Database { virtual bool updateContractData(const cs::Bytes& key, const cs::Bytes& data) = 0; virtual bool getContractData(const cs::Bytes& key, cs::Bytes& data) = 0; + // anchor buffered writes to disk at checkpoint boundaries; default no-op (BDB has its own loop) + virtual bool flush() { return true; } + class Iterator { protected: Iterator(); diff --git a/csdb/include/csdb/database_rocksdb.hpp b/csdb/include/csdb/database_rocksdb.hpp new file mode 100644 index 000000000..43b92221e --- /dev/null +++ b/csdb/include/csdb/database_rocksdb.hpp @@ -0,0 +1,71 @@ +// RocksDB-backed csdb::Database. CFs: blocks, seq_no, contracts. +// blocks keyed BE(seq+1). Selected via -DCSDB_BACKEND=rocksdb. + +#ifndef _CREDITS_CSDB_DATABASE_ROCKSDB_H_INCLUDED_ +#define _CREDITS_CSDB_DATABASE_ROCKSDB_H_INCLUDED_ + +#include +#include + +#include + +namespace rocksdb { +class DB; +class ColumnFamilyHandle; +class Status; +} // namespace rocksdb + +namespace csdb { + +class DatabaseRocksDB : public Database { +public: + DatabaseRocksDB(); + ~DatabaseRocksDB() override; + +public: + // Bulk-load mode (before open): no auto-compaction, disableWAL; caller must compact_full() before close. + void set_bulk_load(bool yes); + // Per-write fsync toggle (default off; durability anchored at checkpoint via flush()). + void set_sync_writes(bool v); + + // Flush + full-range compaction across all CFs; call once after a bulk-load run. + bool compact_full(); + + // Override RocksDB cache/memtable budget (before open); 0 keeps defaults (1 GiB / 256 MiB). + void set_tuning(uint64_t block_cache_bytes, uint64_t memtable_bytes); + + bool open(const std::string& path); + +private: + bool is_open() const final; + bool put(const cs::Bytes& key, uint32_t seq_no, const cs::Bytes& value) final; + bool put_batch(const std::vector& items) final; + bool get(const cs::Bytes& key, cs::Bytes* value) final; + bool get(const uint32_t seq_no, cs::Bytes* value) final; + bool remove(const cs::Bytes&) final; + bool seq_no(const cs::Bytes& key, uint32_t* value) final; + bool write_batch(const ItemList&) final; + IteratorPtr new_iterator() final; + + bool updateContractData(const cs::Bytes& key, const cs::Bytes& data) override; + bool getContractData(const cs::Bytes& key, cs::Bytes& data) override; + bool flush() override; + +private: + class Iterator; + void set_last_error_from_status(const rocksdb::Status& s); + +private: + std::unique_ptr db_; + rocksdb::ColumnFamilyHandle* cf_blocks_ = nullptr; // default CF + rocksdb::ColumnFamilyHandle* cf_seq_no_ = nullptr; + rocksdb::ColumnFamilyHandle* cf_contracts_ = nullptr; + bool bulk_load_ = false; + bool sync_writes_ = false; // matches BDB's DB_TXN_NOSYNC; durability anchored at checkpoint via flush() + uint64_t block_cache_bytes_ = 1ULL << 30; // 1 GiB + uint64_t memtable_bytes_ = 256ULL << 20; // 256 MiB +}; + +} // namespace csdb + +#endif // _CREDITS_CSDB_DATABASE_ROCKSDB_H_INCLUDED_ diff --git a/csdb/include/csdb/storage.hpp b/csdb/include/csdb/storage.hpp index 192d1764c..23704f216 100644 --- a/csdb/include/csdb/storage.hpp +++ b/csdb/include/csdb/storage.hpp @@ -99,6 +99,9 @@ class Storage final { ::std::shared_ptr db; ::cs::Sequence newBlockchainTop = ::cs::kWrongSequence; ::cs::Sequence startSequence = 0; + // 0 = leave Storage defaults (5000 / 100) untouched. + size_t asyncWriteQueueMax = 0; + size_t writeBatchSize = 0; }; struct OpenProgress { @@ -140,7 +143,12 @@ class Storage final { bool open(const ::std::string& path_to_base = ::std::string{}, OpenCallback callback = nullptr, cs::Sequence newBlockchainTop = cs::kWrongSequence, - cs::Sequence startReadFrom = 0); + cs::Sequence startReadFrom = 0, + size_t asyncWriteQueueMax = 5000, + size_t writeBatchSize = 100, + uint64_t rocksDbBlockCacheBytes = 0, + uint64_t rocksDbMemtableBytes = 0, + const std::string& dbBackend = std::string{}); /** * @brief Creating the storage using the parameters set. @@ -170,6 +178,9 @@ class Storage final { */ void close(); + // Anchor buffered writes to disk (RocksDB SyncWAL; BerkeleyDB no-op). Call at checkpoint boundaries. + bool flush(); + /** * @brief Last block hash * @return Last block hash diff --git a/csdb/src/database.cpp b/csdb/src/database.cpp index a81e45216..c5307ebee 100644 --- a/csdb/src/database.cpp +++ b/csdb/src/database.cpp @@ -26,6 +26,15 @@ Database::Iterator::Iterator() = default; Database::Iterator::~Iterator() = default; +bool Database::put_batch(const std::vector& items) { + for (const auto& item : items) { + if (!put(item.hash_key, item.seq_no, item.payload)) { + return false; + } + } + return true; +} + Database::Error Database::last_error() const { return last_error_map(this).last_error_; } diff --git a/csdb/src/database_rocksdb.cpp b/csdb/src/database_rocksdb.cpp new file mode 100644 index 000000000..f4dbe8970 --- /dev/null +++ b/csdb/src/database_rocksdb.cpp @@ -0,0 +1,454 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace csdb { +namespace { + +constexpr const char* kCfSeqNo = "seq_no"; +constexpr const char* kCfContracts = "contracts"; + +// Big-endian so RocksDB's lex order matches numeric order. +inline std::string PackBE32(uint32_t v) { + char b[4]; + b[0] = static_cast((v >> 24) & 0xFF); + b[1] = static_cast((v >> 16) & 0xFF); + b[2] = static_cast((v >> 8) & 0xFF); + b[3] = static_cast( v & 0xFF); + return std::string(b, 4); +} + +inline uint32_t UnpackBE32(const rocksdb::Slice& s) { + if (s.size() != 4) return std::numeric_limits::max(); + const auto* b = reinterpret_cast(s.data()); + return (uint32_t(b[0]) << 24) | (uint32_t(b[1]) << 16) | + (uint32_t(b[2]) << 8) | (uint32_t(b[3]) ); +} + +inline rocksdb::Slice BytesSlice(const cs::Bytes& v) { + return rocksdb::Slice(reinterpret_cast(v.data()), v.size()); +} + +inline cs::Bytes FromString(const std::string& s) { + cs::Bytes out(s.size()); + if (!s.empty()) std::memcpy(out.data(), s.data(), s.size()); + return out; +} + +} // namespace + +DatabaseRocksDB::DatabaseRocksDB() = default; + +DatabaseRocksDB::~DatabaseRocksDB() { + if (db_) { + if (!bulk_load_) { + db_->SyncWAL(); + rocksdb::FlushOptions fo; + fo.wait = true; + for (auto* cf : {cf_blocks_, cf_seq_no_, cf_contracts_}) + if (cf) db_->Flush(fo, cf); + } + if (cf_blocks_) db_->DestroyColumnFamilyHandle(cf_blocks_); + if (cf_seq_no_) db_->DestroyColumnFamilyHandle(cf_seq_no_); + if (cf_contracts_) db_->DestroyColumnFamilyHandle(cf_contracts_); + cf_blocks_ = cf_seq_no_ = cf_contracts_ = nullptr; + } +} + +void DatabaseRocksDB::set_last_error_from_status(const rocksdb::Status& s) { + if (s.ok()) { set_last_error(); return; } + Error err = UnknownError; + if (s.IsNotFound()) err = NotFound; + else if (s.IsCorruption()) err = Corruption; + else if (s.IsNotSupported()) err = NotSupported; + else if (s.IsInvalidArgument()) err = InvalidArgument; + else if (s.IsIOError()) err = IOError; + set_last_error(err, "RocksDB error: %s", s.ToString().c_str()); +} + +void DatabaseRocksDB::set_tuning(uint64_t block_cache_bytes, uint64_t memtable_bytes) { + if (block_cache_bytes > 0) block_cache_bytes_ = block_cache_bytes; + if (memtable_bytes > 0) memtable_bytes_ = memtable_bytes; +} + +bool DatabaseRocksDB::open(const std::string& path) { + boost::filesystem::path direc(path); + if (boost::filesystem::exists(direc)) { + if (!boost::filesystem::is_directory(direc)) return false; + } else { + if (!boost::filesystem::create_directories(direc)) return false; + } + + rocksdb::Options options; + options.create_if_missing = true; + options.create_missing_column_families = true; + options.compression = rocksdb::kLZ4Compression; + options.bottommost_compression = rocksdb::kLZ4HCCompression; + options.level_compaction_dynamic_level_bytes = true; + options.max_background_jobs = 8; + options.write_buffer_size = memtable_bytes_; + options.bytes_per_sync = 1u << 20; + options.wal_bytes_per_sync = 1u << 20; + options.wal_recovery_mode = rocksdb::WALRecoveryMode::kPointInTimeRecovery; + options.paranoid_checks = true; + options.compaction_pri = rocksdb::kMinOverlappingRatio; + options.enable_pipelined_write = true; + options.compaction_readahead_size = 2u << 20; + options.max_open_files = -1; + + if (bulk_load_) { + options.PrepareForBulkLoad(); + } + + auto block_cache = rocksdb::NewLRUCache(block_cache_bytes_); + + auto make_table_factory = [&](size_t block_size, bool partition) { + rocksdb::BlockBasedTableOptions topt; + topt.block_size = block_size; + topt.block_cache = block_cache; + topt.cache_index_and_filter_blocks = true; + topt.pin_l0_filter_and_index_blocks_in_cache = true; + topt.cache_index_and_filter_blocks_with_high_priority = true; + topt.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false)); + topt.format_version = 5; + topt.optimize_filters_for_memory = true; + if (partition) { + // Top-level only resident; sub-partitions cache on demand. + topt.index_type = rocksdb::BlockBasedTableOptions::kTwoLevelIndexSearch; + topt.partition_filters = true; + topt.metadata_block_size = 4096; + topt.pin_top_level_index_and_filter = true; + } + return std::shared_ptr(rocksdb::NewBlockBasedTableFactory(topt)); + }; + + rocksdb::ColumnFamilyOptions cf_blocks_opts(options); + cf_blocks_opts.table_factory = make_table_factory(16 * 1024, false); + cf_blocks_opts.target_file_size_base = 256ULL << 20; + + rocksdb::ColumnFamilyOptions cf_seq_no_opts(options); + cf_seq_no_opts.table_factory = make_table_factory(4 * 1024, true); + cf_seq_no_opts.write_buffer_size = 32ULL << 20; + + rocksdb::ColumnFamilyOptions cf_contracts_opts(options); + cf_contracts_opts.table_factory = make_table_factory(16 * 1024, false); + cf_contracts_opts.write_buffer_size = 32ULL << 20; + + std::vector cfs = { + {rocksdb::kDefaultColumnFamilyName, cf_blocks_opts}, + {kCfSeqNo, cf_seq_no_opts}, + {kCfContracts, cf_contracts_opts}, + }; + std::vector handles; + + rocksdb::DB* raw_db = nullptr; + auto s = rocksdb::DB::Open(options, path, cfs, &handles, &raw_db); + if (!s.ok()) { + set_last_error_from_status(s); + return false; + } + + db_.reset(raw_db); + cf_blocks_ = handles[0]; + cf_seq_no_ = handles[1]; + cf_contracts_ = handles[2]; + + set_last_error(); + return true; +} + +bool DatabaseRocksDB::is_open() const { + return db_ != nullptr; +} + +bool DatabaseRocksDB::put(const cs::Bytes& key, uint32_t seq_no, const cs::Bytes& value) { + if (!db_) { set_last_error(NotOpen); return false; } + + const std::string be_key = PackBE32(seq_no + 1); + // seq_no CF stores native-endian uint32 to match BDB byte pattern. + const uint32_t native_seq = seq_no + 1; + + rocksdb::WriteBatch batch; + batch.Put(cf_blocks_, rocksdb::Slice(be_key), BytesSlice(value)); + batch.Put(cf_seq_no_, BytesSlice(key), + rocksdb::Slice(reinterpret_cast(&native_seq), sizeof(native_seq))); + + rocksdb::WriteOptions wo; + wo.sync = sync_writes_ && !bulk_load_; + wo.disableWAL = bulk_load_; + auto s = db_->Write(wo, &batch); + if (!s.ok()) { set_last_error_from_status(s); return false; } + set_last_error(); + return true; +} + +bool DatabaseRocksDB::put_batch(const std::vector& items) { + if (!db_) { set_last_error(NotOpen); return false; } + if (items.empty()) { set_last_error(); return true; } + + // One WriteBatch over 2*N puts (blocks + seq_no), amortizing per-Write overhead. + rocksdb::WriteBatch batch; + std::vector be_keys; + std::vector native_seqs; + be_keys.reserve(items.size()); + native_seqs.reserve(items.size()); + + for (const auto& item : items) { + be_keys.push_back(PackBE32(item.seq_no + 1)); + native_seqs.push_back(item.seq_no + 1); + batch.Put(cf_blocks_, rocksdb::Slice(be_keys.back()), BytesSlice(item.payload)); + batch.Put(cf_seq_no_, BytesSlice(item.hash_key), + rocksdb::Slice(reinterpret_cast(&native_seqs.back()), sizeof(uint32_t))); + } + + rocksdb::WriteOptions wo; + wo.sync = sync_writes_ && !bulk_load_; + wo.disableWAL = bulk_load_; + auto s = db_->Write(wo, &batch); + if (!s.ok()) { set_last_error_from_status(s); return false; } + set_last_error(); + return true; +} + +void DatabaseRocksDB::set_bulk_load(bool yes) { + bulk_load_ = yes; +} + +void DatabaseRocksDB::set_sync_writes(bool v) { + sync_writes_ = v; +} + +bool DatabaseRocksDB::compact_full() { + if (!db_) { set_last_error(NotOpen); return false; } + rocksdb::FlushOptions fo; + fo.wait = true; + for (auto* cf : {cf_blocks_, cf_seq_no_, cf_contracts_}) { + if (!cf) continue; + auto s = db_->Flush(fo, cf); + if (!s.ok()) { set_last_error_from_status(s); return false; } + } + rocksdb::CompactRangeOptions co; + co.exclusive_manual_compaction = true; + for (auto* cf : {cf_blocks_, cf_seq_no_, cf_contracts_}) { + if (!cf) continue; + auto s = db_->CompactRange(co, cf, nullptr, nullptr); + if (!s.ok()) { set_last_error_from_status(s); return false; } + } + set_last_error(); + return true; +} + +bool DatabaseRocksDB::get(const cs::Bytes& key, cs::Bytes* value) { + if (!db_) { set_last_error(NotOpen); return false; } + + if (value == nullptr) { + // Existence probe on the seq_no CF (matches BDB's exists() check). + std::string scratch; + auto s = db_->Get(rocksdb::ReadOptions(), cf_seq_no_, BytesSlice(key), &scratch); + return s.ok(); + } + + std::string seq_no_bytes; + auto s = db_->Get(rocksdb::ReadOptions(), cf_seq_no_, BytesSlice(key), &seq_no_bytes); + if (!s.ok()) { set_last_error_from_status(s); return false; } + if (seq_no_bytes.size() != sizeof(uint32_t)) { + set_last_error(Corruption, "seq_no entry has unexpected size"); + return false; + } + + uint32_t native_seq = 0; + std::memcpy(&native_seq, seq_no_bytes.data(), sizeof(native_seq)); + + const std::string be_key = PackBE32(native_seq); + std::string blob; + s = db_->Get(rocksdb::ReadOptions(), cf_blocks_, rocksdb::Slice(be_key), &blob); + if (!s.ok()) { set_last_error_from_status(s); return false; } + + *value = FromString(blob); + set_last_error(); + return true; +} + +bool DatabaseRocksDB::seq_no(const cs::Bytes& key, uint32_t* value) { + if (value == nullptr) { set_last_error(InvalidArgument); return false; } + if (!db_) { set_last_error(NotOpen); return false; } + + std::string buf; + auto s = db_->Get(rocksdb::ReadOptions(), cf_seq_no_, BytesSlice(key), &buf); + if (!s.ok()) { set_last_error_from_status(s); return false; } + if (buf.size() != sizeof(uint32_t)) { + set_last_error(Corruption, "seq_no entry has unexpected size"); + return false; + } + std::memcpy(value, buf.data(), sizeof(uint32_t)); + return true; +} + +bool DatabaseRocksDB::get(const uint32_t seq_no, cs::Bytes* value) { + if (!db_) { set_last_error(NotOpen); return false; } + if (value == nullptr) return false; + + const std::string be_key = PackBE32(seq_no + 1); + std::string blob; + auto s = db_->Get(rocksdb::ReadOptions(), cf_blocks_, rocksdb::Slice(be_key), &blob); + if (!s.ok()) { set_last_error_from_status(s); return false; } + + *value = FromString(blob); + set_last_error(); + return true; +} + +bool DatabaseRocksDB::remove(const cs::Bytes& key) { + if (!db_) { set_last_error(NotOpen); return false; } + + std::string seq_no_bytes; + auto s = db_->Get(rocksdb::ReadOptions(), cf_seq_no_, BytesSlice(key), &seq_no_bytes); + if (!s.ok()) { set_last_error_from_status(s); return false; } + if (seq_no_bytes.size() != sizeof(uint32_t)) { + set_last_error(Corruption, "seq_no entry has unexpected size"); + return false; + } + uint32_t native_seq = 0; + std::memcpy(&native_seq, seq_no_bytes.data(), sizeof(native_seq)); + const std::string be_key = PackBE32(native_seq); + + rocksdb::WriteBatch batch; + batch.Delete(cf_seq_no_, BytesSlice(key)); + batch.Delete(cf_blocks_, rocksdb::Slice(be_key)); + rocksdb::WriteOptions wo; + wo.sync = sync_writes_ && !bulk_load_; + wo.disableWAL = bulk_load_; + s = db_->Write(wo, &batch); + if (!s.ok()) { set_last_error_from_status(s); return false; } + + set_last_error(); + return true; +} + +bool DatabaseRocksDB::write_batch(const ItemList&) { + assert(false); // unimplemented; mirrors BDB + if (!db_) { set_last_error(NotOpen); return false; } + set_last_error(); + return true; +} + +class DatabaseRocksDB::Iterator final : public Database::Iterator { +public: + explicit Iterator(rocksdb::Iterator* it) : it_(it), valid_(it != nullptr) {} + ~Iterator() final { delete it_; } + + bool is_valid() const final { return valid_ && it_ && it_->Valid(); } + + void seek_to_first() final { + if (!it_) return; + it_->SeekToFirst(); + valid_ = it_->Valid(); + } + + void seek_to_last() final { + if (!it_) return; + it_->SeekToLast(); + valid_ = it_->Valid(); + } + + void seek(const cs::Bytes&) final { + // BDB equivalent is also asserted-out; preserve the contract. + assert(false); + } + + void seek(const uint32_t seq_no) final { + if (!it_) return; + const std::string be_key = PackBE32(seq_no + 1); + it_->Seek(rocksdb::Slice(be_key)); + // BDB DB_SET semantics: only valid on exact match. + if (it_->Valid() && it_->key().size() == 4 && it_->key().compare(rocksdb::Slice(be_key)) == 0) { + valid_ = true; + } else { + valid_ = false; + } + } + + void next() final { + if (!it_) return; + it_->Next(); + valid_ = it_->Valid(); + } + + void prev() final { + // BDB equivalent is also asserted-out; preserve the contract. + assert(false); + } + + uint32_t key() const final { + if (!valid_ || !it_) return std::numeric_limits::max(); + const auto v = UnpackBE32(it_->key()); + // Storage convention is 1-based (seq_no+1); callers expect 0-based. + return (v == std::numeric_limits::max()) ? v : v - 1; + } + + cs::Bytes value() const final { + if (!valid_ || !it_) return cs::Bytes{}; + const auto v = it_->value(); + cs::Bytes out(v.size()); + if (!v.empty()) std::memcpy(out.data(), v.data(), v.size()); + return out; + } + +private: + rocksdb::Iterator* it_; + bool valid_; +}; + +DatabaseRocksDB::IteratorPtr DatabaseRocksDB::new_iterator() { + if (!db_) { + set_last_error(NotOpen); + return nullptr; + } + return Database::IteratorPtr(new DatabaseRocksDB::Iterator( + db_->NewIterator(rocksdb::ReadOptions(), cf_blocks_))); +} + +bool DatabaseRocksDB::flush() { + if (!db_) { set_last_error(NotOpen); return false; } + auto s = db_->SyncWAL(); + if (!s.ok()) { set_last_error_from_status(s); return false; } + set_last_error(); + return true; +} + +bool DatabaseRocksDB::updateContractData(const cs::Bytes& key, const cs::Bytes& data) { + if (!db_) { set_last_error(NotOpen); return false; } + rocksdb::WriteOptions wo; + wo.sync = sync_writes_ && !bulk_load_; + wo.disableWAL = bulk_load_; + auto s = db_->Put(wo, cf_contracts_, BytesSlice(key), BytesSlice(data)); + if (!s.ok()) { set_last_error_from_status(s); return false; } + set_last_error(); + return true; +} + +bool DatabaseRocksDB::getContractData(const cs::Bytes& key, cs::Bytes& data) { + if (!db_) { set_last_error(NotOpen); return false; } + std::string buf; + auto s = db_->Get(rocksdb::ReadOptions(), cf_contracts_, BytesSlice(key), &buf); + if (!s.ok()) { set_last_error_from_status(s); return false; } + data = FromString(buf); + set_last_error(); + return true; +} + +} // namespace csdb diff --git a/csdb/src/storage.cpp b/csdb/src/storage.cpp index 442f4c437..1453c431e 100644 --- a/csdb/src/storage.cpp +++ b/csdb/src/storage.cpp @@ -20,7 +20,14 @@ #include #include +#if defined(CSDB_USE_ROCKSDB) && defined(CSDB_USE_BERKELEYDB) #include +#include +#elif defined(CSDB_USE_ROCKSDB) +#include +#else +#include +#endif #include #include #include @@ -122,8 +129,12 @@ class Storage::priv { ~priv() { if (write_thread.joinable()) { - quit = true; - write_cond_var.notify_one(); + // writer drains write_queue before honoring quit. + { + std::unique_lock lock(write_lock); + quit = true; + } + write_cond_var.notify_all(); write_thread.join(); } } @@ -145,9 +156,14 @@ class Storage::priv { std::mutex data_lock; + // Async write pipeline. Bounded; full queue back-pressures producers. + // Defaults overridable via Storage::open(...). + size_t asyncWriteQueueMax_ = 5000; + size_t writeBatchSize_ = 100; // pools coalesced into one db->put_batch std::deque write_queue; std::mutex write_lock; - std::condition_variable write_cond_var; + std::condition_variable write_cond_var; // queue non-empty + std::condition_variable queue_space_cond; // queue has space struct PoolElement { cs::Sequence seq; struct bySequence {}; @@ -330,22 +346,43 @@ bool Storage::priv::rescan(Storage::OpenCallback callback) { } void Storage::priv::write_routine() { - std::unique_lock lock(write_lock); - while (!quit) { - write_cond_var.wait(lock); - while (!write_queue.empty()) { - Pool& pool = write_queue.front(); - if (!pool.is_read_only()) { - if (!pool.compose()) { - set_last_error(Storage::DataIntegrityError, "Pool passed to storage is not composed and failed to compose now"); - } - } - const PoolHash hash = pool.hash(); + while (true) { + std::unique_lock lock(write_lock); + write_cond_var.wait(lock, [this] { return quit || !write_queue.empty(); }); - db->put(hash.to_binary(), static_cast(pool.sequence()), pool.to_binary()); + if (write_queue.empty() && quit) { + return; + } + + // Peek (don't pop) up to writeBatchSize_ pools; write_queue_search needs them until I/O completes. + std::vector pools; + const size_t take = std::min(writeBatchSize_, write_queue.size()); + pools.reserve(take); + auto it = write_queue.begin(); + for (size_t i = 0; i < take; ++i, ++it) { + pools.push_back(*it); + } + // Build payloads outside the lock; one put_batch coalesces 2*N puts into a single WriteBatch. + lock.unlock(); + + std::vector requests; + requests.reserve(pools.size()); + for (auto& pool : pools) { + requests.push_back(Database::PendingWrite{ + pool.hash().to_binary(), + static_cast(pool.sequence()), + pool.to_binary() + }); + } + + db->put_batch(requests); + + lock.lock(); + for (size_t i = 0; i < pools.size(); ++i) { write_queue.pop_front(); } + queue_space_cond.notify_all(); } } @@ -423,6 +460,18 @@ bool Storage::open(const OpenOptions& opt, OpenCallback callback) { return false; } + if (opt.asyncWriteQueueMax > 0) { + d->asyncWriteQueueMax_ = opt.asyncWriteQueueMax; + } + if (opt.writeBatchSize > 0) { + d->writeBatchSize_ = opt.writeBatchSize; + } + + // Start async writer (idempotent: re-opens won't spawn a second thread). + if (!d->write_thread.joinable()) { + d->write_thread = std::thread(&Storage::priv::write_routine, d.get()); + } + if (opt.newBlockchainTop != cs::kWrongSequence) { auto seqToRemove = static_cast(opt.newBlockchainTop + 1); auto seqLast = seqToRemove; @@ -496,22 +545,71 @@ bool Storage::open( const ::std::string& path_to_base, OpenCallback callback, cs::Sequence newBlockchainTop, - cs::Sequence startReadFrom + cs::Sequence startReadFrom, + size_t asyncWriteQueueMax, + size_t writeBatchSize, + uint64_t rocksDbBlockCacheBytes, + uint64_t rocksDbMemtableBytes, + const std::string& dbBackend ) { ::std::string path{path_to_base}; if (path.empty()) { path = ::csdb::internal::app_data_path() + "/CREDITS"; } - auto db{::std::make_shared<::csdb::DatabaseBerkeleyDB>()}; - db->open(path); - - //d->write_thread = std::thread(&Storage::priv::write_routine, d.get()); + if (asyncWriteQueueMax > 0) { + d->asyncWriteQueueMax_ = asyncWriteQueueMax; + } + if (writeBatchSize > 0) { + d->writeBatchSize_ = writeBatchSize; + } + + std::shared_ptr db; +#if defined(CSDB_USE_ROCKSDB) && defined(CSDB_USE_BERKELEYDB) + // Default berkeleydb; rocksdb is opt-in via [storage] db_backend = rocksdb. + if (dbBackend == "rocksdb" || dbBackend == "rocks") { + auto rocks = std::make_shared(); + rocks->set_tuning(rocksDbBlockCacheBytes, rocksDbMemtableBytes); + rocks->open(path); + db = rocks; + } else { + auto bdb = std::make_shared(); + bdb->open(path); + db = bdb; + } +#elif defined(CSDB_USE_ROCKSDB) + auto rocks = std::make_shared(); + rocks->set_tuning(rocksDbBlockCacheBytes, rocksDbMemtableBytes); + rocks->open(path); + db = rocks; + (void)dbBackend; +#else + auto bdb = std::make_shared(); + bdb->open(path); + db = bdb; + (void)rocksDbBlockCacheBytes; + (void)rocksDbMemtableBytes; + (void)dbBackend; +#endif + OpenOptions opt{db, newBlockchainTop, startReadFrom}; + return open(opt, callback); +} - return open(OpenOptions{db, newBlockchainTop, startReadFrom}, callback); +bool Storage::flush() { + if (!d || !d->db) return false; + return d->db->flush(); } void Storage::close() { + // Drain the async writer before destroying the db to avoid use-after-free in RocksDB's dtor. + if (d->write_thread.joinable()) { + { + std::unique_lock lock(d->write_lock); + d->quit = true; + } + d->write_cond_var.notify_all(); + d->write_thread.join(); + } d->db.reset(); d->set_last_error(); } @@ -552,14 +650,16 @@ bool Storage::pool_save(Pool pool) { d->set_last_error(InvalidParameter, "%s: Trying to save pool with another prev hash [hash: %s]", funcName(), pool.previous_hash().to_string().c_str()); return false; } - /* - { + + // Hand off to write_routine; backpressure if queue full. + { std::unique_lock lock(d->write_lock); + d->queue_space_cond.wait(lock, [this] { + return d->write_queue.size() < d->asyncWriteQueueMax_; + }); d->write_queue.push_back(pool); d->write_cond_var.notify_one(); - } - */ - d->db->put(hash.to_binary(), static_cast(pool.sequence()), pool.to_binary()); + } { std::unique_lock lock(d->data_lock); @@ -720,7 +820,9 @@ Pool Storage::pool_load(const cs::Sequence sequence) const { if (needParseData) { res = Pool::from_binary(std::move(data)); - d->pools_cache_insert(res.sequence(), res.hash(), res); + if (res.is_valid()) { + d->pools_cache_insert(res.sequence(), res.hash(), res); + } } if (!res.is_valid()) { diff --git a/csnode/src/blockchain.cpp b/csnode/src/blockchain.cpp index 274d3a30c..2c6fa245a 100644 --- a/csnode/src/blockchain.cpp +++ b/csnode/src/blockchain.cpp @@ -173,7 +173,12 @@ bool BlockChain::init( return false; }; - if (!storage_.open(path, progress, newBlockchainTop, firstBlockToReadInDatabase)) { + const auto& storageCfg = cs::ConfigHolder::instance().config()->getStorageSettings(); + if (!storage_.open(path, progress, newBlockchainTop, firstBlockToReadInDatabase, + storageCfg.asyncWriteQueueSize, storageCfg.writeBatchSize, + static_cast(storageCfg.rocksdbBlockCacheMb) << 20, + static_cast(storageCfg.rocksdbMemtableMb) << 20, + storageCfg.dbBackend)) { cserror() << kLogPrefix << "Couldn't open database at " << path; return false; } diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index c4825f901..124d78df2 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -6,7 +6,58 @@ add_subdirectory(thrift) configure_third_party_compiler_flags() -add_subdirectory(berkeleydb) +# lz4: used by csdb directly and (below) handed to rocksdb as its LZ4. add_subdirectory(lz4) + +# CSDB_BACKEND selects which backend subdirs to build. +set(_csdb_need_bdb OFF) +set(_csdb_need_rocksdb OFF) +if(CSDB_BACKEND STREQUAL "berkeleydb") + set(_csdb_need_bdb ON) +elseif(CSDB_BACKEND STREQUAL "rocksdb") + set(_csdb_need_rocksdb ON) +elseif(CSDB_BACKEND STREQUAL "both") + set(_csdb_need_bdb ON) + set(_csdb_need_rocksdb ON) +endif() + +if(_csdb_need_bdb) + add_subdirectory(berkeleydb) +endif() + +if(_csdb_need_rocksdb) + set(WITH_TESTS OFF CACHE BOOL "" FORCE) + set(WITH_TOOLS OFF CACHE BOOL "" FORCE) + set(WITH_CORE_TOOLS OFF CACHE BOOL "" FORCE) + set(WITH_BENCHMARK_TOOLS OFF CACHE BOOL "" FORCE) + set(WITH_ALL_TESTS OFF CACHE BOOL "" FORCE) + set(WITH_TRACE_TOOLS OFF CACHE BOOL "" FORCE) + set(WITH_GFLAGS OFF CACHE BOOL "" FORCE) + set(WITH_JNI OFF CACHE BOOL "" FORCE) + set(WITH_EXAMPLES OFF CACHE BOOL "" FORCE) + set(ROCKSDB_BUILD_SHARED OFF CACHE BOOL "" FORCE) + set(FAIL_ON_WARNINGS OFF CACHE BOOL "" FORCE) + set(USE_RTTI ON CACHE BOOL "" FORCE) + set(WITH_MD_LIBRARY OFF CACHE BOOL "" FORCE) # /MT to match project + set(PORTABLE "1" CACHE STRING "" FORCE) # baseline x86-64 + + # Hand internal lz4 to rocksdb. On MSVC, thirdparty.inc honors only env vars. + set(WITH_LZ4 ON CACHE BOOL "" FORCE) + set(ENV{LZ4_INCLUDE} "${CMAKE_CURRENT_SOURCE_DIR}/lz4") + set(ENV{LZ4_LIB_DEBUG} "lz4") + set(ENV{LZ4_LIB_RELEASE} "lz4") + + # rocksdb env_win.cc needs Win8+ (FILE_ID_INFO); shadow project's 0x0601. + if(WIN32) + remove_definitions(-D_WIN32_WINNT=0x0601) + add_definitions(-D_WIN32_WINNT=0x0A00) + endif() + add_subdirectory(rocksdb) + if(WIN32) + remove_definitions(-D_WIN32_WINNT=0x0A00) + add_definitions(-D_WIN32_WINNT=0x0601) + endif() +endif() + add_subdirectory(rang) add_subdirectory(nameof) diff --git a/third-party/lz4/CMakeLists.txt b/third-party/lz4/CMakeLists.txt index 0839d6c83..bcead1735 100644 --- a/third-party/lz4/CMakeLists.txt +++ b/third-party/lz4/CMakeLists.txt @@ -4,4 +4,9 @@ project (lz4) add_library(${PROJECT_NAME} lz4.h - lz4.c ) + lz4.c + lz4hc.h + lz4hc.c ) + +# Propagate -I to consumers via target_link_libraries. +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/third-party/lz4/lz4hc.c b/third-party/lz4/lz4hc.c new file mode 100644 index 000000000..8108ea011 --- /dev/null +++ b/third-party/lz4/lz4hc.c @@ -0,0 +1,1409 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +/* note : lz4hc is not an independent module, it requires lz4.h/lz4.c for proper compilation */ + + +/* ************************************* +* Tuning Parameter +***************************************/ + +/*! HEAPMODE : + * Select how default compression function will allocate workplace memory, + * in stack (0:fastest), or in heap (1:requires malloc()). + * Since workplace is rather large, heap mode is recommended. + */ +#ifndef LZ4HC_HEAPMODE +# define LZ4HC_HEAPMODE 1 +#endif + + +/*=== Dependency ===*/ +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" + + +/*=== Common LZ4 definitions ===*/ +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined (__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +#define LZ4_COMMONDEFS_ONLY +#include "lz4.c" /* LZ4_count, constants, mem */ + + +/*=== Constants ===*/ +#define OPTIMAL_ML (int)((ML_MASK-1)+MINMATCH) +#define LZ4_OPT_NUM (1<<12) + + +/*=== Macros ===*/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +#define MAX(a,b) ( (a) > (b) ? (a) : (b) ) +#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) +#define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ +#define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ + +static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } + +/*=== Enums ===*/ +typedef enum { noDictCtx, usingDictCtx } dictCtx_directive; + + +/************************************** +* HC Compression +**************************************/ +static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) +{ + MEM_INIT((void*)hc4->hashTable, 0, sizeof(hc4->hashTable)); + MEM_INIT(hc4->chainTable, 0xFF, sizeof(hc4->chainTable)); +} + +static void LZ4HC_init (LZ4HC_CCtx_internal* hc4, const BYTE* start) +{ + uptrval startingOffset = hc4->end - hc4->base; + if (startingOffset > 1 GB) { + LZ4HC_clearTables(hc4); + startingOffset = 0; + } + startingOffset += 64 KB; + hc4->nextToUpdate = (U32) startingOffset; + hc4->base = start - startingOffset; + hc4->end = start; + hc4->dictBase = start - startingOffset; + hc4->dictLimit = (U32) startingOffset; + hc4->lowLimit = (U32) startingOffset; +} + + +/* Update chains up to ip (excluded) */ +LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) +{ + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + U32 const target = (U32)(ip - base); + U32 idx = hc4->nextToUpdate; + + while (idx < target) { + U32 const h = LZ4HC_hashPtr(base+idx); + size_t delta = idx - hashTable[h]; + if (delta>MAX_DISTANCE) delta = MAX_DISTANCE; + DELTANEXTU16(chainTable, idx) = (U16)delta; + hashTable[h] = idx; + idx++; + } + + hc4->nextToUpdate = target; +} + +/** LZ4HC_countBack() : + * @return : negative value, nb of common bytes before ip/match */ +LZ4_FORCE_INLINE +int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, + const BYTE* const iMin, const BYTE* const mMin) +{ + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + while ( (back > min) + && (ip[back-1] == match[back-1]) ) + back--; + return back; +} + +/* LZ4HC_countPattern() : + * pattern32 must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) */ +static unsigned +LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) +{ + const BYTE* const iStart = ip; + reg_t const pattern = (sizeof(pattern)==8) ? (reg_t)pattern32 + (((reg_t)pattern32) << 32) : pattern32; + + while (likely(ip < iEnd-(sizeof(pattern)-1))) { + reg_t const diff = LZ4_read_ARCH(ip) ^ pattern; + if (!diff) { ip+=sizeof(pattern); continue; } + ip += LZ4_NbCommonBytes(diff); + return (unsigned)(ip - iStart); + } + + if (LZ4_isLittleEndian()) { + reg_t patternByte = pattern; + while ((ip>= 8; + } + } else { /* big endian */ + U32 bitOffset = (sizeof(pattern)*8) - 8; + while (ip < iEnd) { + BYTE const byte = (BYTE)(pattern >> bitOffset); + if (*ip != byte) break; + ip ++; bitOffset -= 8; + } + } + + return (unsigned)(ip - iStart); +} + +/* LZ4HC_reverseCountPattern() : + * pattern must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) + * read using natural platform endianess */ +static unsigned +LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) +{ + const BYTE* const iStart = ip; + + while (likely(ip >= iLow+4)) { + if (LZ4_read32(ip-4) != pattern) break; + ip -= 4; + } + { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianess */ + while (likely(ip>iLow)) { + if (ip[-1] != *bytePtr) break; + ip--; bytePtr--; + } } + return (unsigned)(iStart - ip); +} + +typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; + +LZ4_FORCE_INLINE int +LZ4HC_InsertAndGetWiderMatch ( + LZ4HC_CCtx_internal* hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, + const BYTE* const iHighLimit, + int longest, + const BYTE** matchpos, + const BYTE** startpos, + const int maxNbAttempts, + const int patternAnalysis, + const int chainSwap, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + U16* const chainTable = hc4->chainTable; + U32* const HashTable = hc4->hashTable; + const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const BYTE* const base = hc4->base; + const U32 dictLimit = hc4->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const U32 ipIndex = (U32)(ip - base); + const U32 lowestMatchIndex = (hc4->lowLimit + 64 KB > ipIndex) ? hc4->lowLimit : ipIndex - MAX_DISTANCE; + const BYTE* const dictBase = hc4->dictBase; + int const lookBackLength = (int)(ip-iLowLimit); + int nbAttempts = maxNbAttempts; + int matchChainPos = 0; + U32 const pattern = LZ4_read32(ip); + U32 matchIndex; + U32 dictMatchIndex; + repeat_state_e repeat = rep_untested; + size_t srcPatternLength = 0; + + DEBUGLOG(7, "LZ4HC_InsertAndGetWiderMatch"); + /* First Match */ + LZ4HC_Insert(hc4, ip); + matchIndex = HashTable[LZ4HC_hashPtr(ip)]; + DEBUGLOG(7, "First match at index %u / %u (lowestMatchIndex)", + matchIndex, lowestMatchIndex); + + while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) { + int matchLength=0; + nbAttempts--; + assert(matchIndex < ipIndex); + if (favorDecSpeed && (ipIndex - matchIndex < 8)) { + /* do nothing */ + } else if (matchIndex >= dictLimit) { /* within current Prefix */ + const BYTE* const matchPtr = base + matchIndex; + assert(matchPtr >= lowPrefixPtr); + assert(matchPtr < ip); + assert(longest >= 1); + if (LZ4_read16(iLowLimit + longest - 1) == LZ4_read16(matchPtr - lookBackLength + longest - 1)) { + if (LZ4_read32(matchPtr) == pattern) { + int const back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, lowPrefixPtr) : 0; + matchLength = MINMATCH + LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = matchPtr + back; + *startpos = ip + back; + } } } + } else { /* lowestMatchIndex <= matchIndex < dictLimit */ + const BYTE* const matchPtr = dictBase + matchIndex; + if (LZ4_read32(matchPtr) == pattern) { + const BYTE* const dictStart = dictBase + hc4->lowLimit; + int back = 0; + const BYTE* vLimit = ip + (dictLimit - matchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + matchLength = LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + if ((ip+matchLength == vLimit) && (vLimit < iHighLimit)) + matchLength += LZ4_count(ip+matchLength, lowPrefixPtr, iHighLimit); + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictStart) : 0; + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = base + matchIndex + back; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip + back; + } } } + + if (chainSwap && matchLength==longest) { /* better match => select a better chain */ + assert(lookBackLength==0); /* search forward only */ + if (matchIndex + longest <= ipIndex) { + U32 distanceToNextMatch = 1; + int pos; + for (pos = 0; pos <= longest - MINMATCH; pos++) { + U32 const candidateDist = DELTANEXTU16(chainTable, matchIndex + pos); + if (candidateDist > distanceToNextMatch) { + distanceToNextMatch = candidateDist; + matchChainPos = pos; + } } + if (distanceToNextMatch > 1) { + if (distanceToNextMatch > matchIndex) break; /* avoid overflow */ + matchIndex -= distanceToNextMatch; + continue; + } } } + + { U32 const distNextMatch = DELTANEXTU16(chainTable, matchIndex); + if (patternAnalysis && distNextMatch==1 && matchChainPos==0) { + U32 const matchCandidateIdx = matchIndex-1; + /* may be a repeated pattern */ + if (repeat == rep_untested) { + if ( ((pattern & 0xFFFF) == (pattern >> 16)) + & ((pattern & 0xFF) == (pattern >> 24)) ) { + repeat = rep_confirmed; + srcPatternLength = LZ4HC_countPattern(ip+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + } else { + repeat = rep_not; + } } + if ( (repeat == rep_confirmed) + && (matchCandidateIdx >= dictLimit) ) { /* same segment only */ + const BYTE* const matchPtr = base + matchCandidateIdx; + if (LZ4_read32(matchPtr) == pattern) { /* good candidate */ + size_t const forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + const BYTE* const lowestMatchPtr = (lowPrefixPtr + MAX_DISTANCE >= ip) ? lowPrefixPtr : ip - MAX_DISTANCE; + size_t const backLength = LZ4HC_reverseCountPattern(matchPtr, lowestMatchPtr, pattern); + size_t const currentSegmentLength = backLength + forwardPatternLength; + + if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ + && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ + matchIndex = matchCandidateIdx + (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ + } else { + matchIndex = matchCandidateIdx - (U32)backLength; /* farthest position in current segment, will find a match of length currentSegmentLength + maybe some back */ + if (lookBackLength==0) { /* no back possible */ + size_t const maxML = MIN(currentSegmentLength, srcPatternLength); + if ((size_t)longest < maxML) { + assert(maxML < 2 GB); + longest = (int)maxML; + *matchpos = base + matchIndex; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip; + } + { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); + if (distToNextPattern > matchIndex) break; /* avoid overflow */ + matchIndex -= distToNextPattern; + } } } + continue; + } } + } } /* PA optimization */ + + /* follow current chain */ + matchIndex -= DELTANEXTU16(chainTable, matchIndex+matchChainPos); + + } /* while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) */ + + if (dict == usingDictCtx && nbAttempts && ipIndex - lowestMatchIndex < MAX_DISTANCE) { + size_t const dictEndOffset = dictCtx->end - dictCtx->base; + assert(dictEndOffset <= 1 GB); + dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + matchIndex = dictMatchIndex + lowestMatchIndex - (U32)dictEndOffset; + while (ipIndex - matchIndex <= MAX_DISTANCE && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->base + dictMatchIndex; + + if (LZ4_read32(matchPtr) == pattern) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (dictEndOffset - dictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->base + dictCtx->dictLimit) : 0; + mlt -= back; + if (mlt > longest) { + longest = mlt; + *matchpos = base + matchIndex + back; + *startpos = ip + back; + } + } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, dictMatchIndex); + dictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } + } + } + + return longest; +} + +LZ4_FORCE_INLINE +int LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ + const BYTE* const ip, const BYTE* const iLimit, + const BYTE** matchpos, + const int maxNbAttempts, + const int patternAnalysis, + const dictCtx_directive dict) +{ + const BYTE* uselessPtr = ip; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, matchpos, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); +} + + + +typedef enum { + noLimit = 0, + limitedOutput = 1, + limitedDestSize = 2, +} limitedOutput_directive; + +/* LZ4HC_encodeSequence() : + * @return : 0 if ok, + * 1 if buffer issue detected */ +LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( + const BYTE** ip, + BYTE** op, + const BYTE** anchor, + int matchLength, + const BYTE* const match, + limitedOutput_directive limit, + BYTE* oend) +{ + size_t length; + BYTE* const token = (*op)++; + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) + static const BYTE* start = NULL; + static U32 totalCost = 0; + U32 const pos = (start==NULL) ? 0 : (U32)(*anchor - start); + U32 const ll = (U32)(*ip - *anchor); + U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; + U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; + U32 const cost = 1 + llAdd + ll + 2 + mlAdd; + if (start==NULL) start = *anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%3u, match:%4i, offset:%5u, cost:%3u + %u", + pos, + (U32)(*ip - *anchor), matchLength, (U32)(*ip-match), + cost, totalCost); + totalCost += cost; +#endif + + /* Encode Literal length */ + length = (size_t)(*ip - *anchor); + if ((limit) && ((*op + (length >> 8) + length + (2 + 1 + LASTLITERALS)) > oend)) return 1; /* Check output limit */ + if (length >= RUN_MASK) { + size_t len = length - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for(; len >= 255 ; len -= 255) *(*op)++ = 255; + *(*op)++ = (BYTE)len; + } else { + *token = (BYTE)(length << ML_BITS); + } + + /* Copy Literals */ + LZ4_wildCopy(*op, *anchor, (*op) + length); + *op += length; + + /* Encode Offset */ + LZ4_writeLE16(*op, (U16)(*ip-match)); *op += 2; + + /* Encode MatchLength */ + assert(matchLength >= MINMATCH); + length = (size_t)(matchLength - MINMATCH); + if ((limit) && (*op + (length >> 8) + (1 + LASTLITERALS) > oend)) return 1; /* Check output limit */ + if (length >= ML_MASK) { + *token += ML_MASK; + length -= ML_MASK; + for(; length >= 510 ; length -= 510) { *(*op)++ = 255; *(*op)++ = 255; } + if (length >= 255) { length -= 255; *(*op)++ = 255; } + *(*op)++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + *ip += matchLength; + *anchor = *ip; + + return 0; +} + +LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( + LZ4HC_CCtx_internal* const ctx, + const char* const source, + char* const dest, + int* srcSizePtr, + int const maxOutputSize, + unsigned maxNbAttempts, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + const int inputSize = *srcSizePtr; + const int patternAnalysis = (maxNbAttempts > 128); /* levels 9+ */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + + BYTE* optr = (BYTE*) dest; + BYTE* op = (BYTE*) dest; + BYTE* oend = op + maxOutputSize; + + int ml0, ml, ml2, ml3; + const BYTE* start0; + const BYTE* ref0; + const BYTE* ref = NULL; + const BYTE* start2 = NULL; + const BYTE* ref2 = NULL; + const BYTE* start3 = NULL; + const BYTE* ref3 = NULL; + + /* init */ + *srcSizePtr = 0; + if (limit == limitedDestSize) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* Main Loop */ + while (ip <= mflimit) { + ml = LZ4HC_InsertAndFindBestMatch (ctx, ip, matchlimit, &ref, maxNbAttempts, patternAnalysis, dict); + if (ml encode ML1 */ + optr = op; + if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + continue; + } + + if (start0 < ip) { /* first match was skipped at least once */ + if (start2 < ip + ml0) { /* squeezing ML1 between ML0(original ML1) and ML2 */ + ip = start0; ref = ref0; ml = ml0; /* restore initial ML1 */ + } } + + /* Here, start0==ip */ + if ((start2 - ip) < 3) { /* First Match too small : removed */ + ml = ml2; + ip = start2; + ref =ref2; + goto _Search2; + } + +_Search3: + /* At this stage, we have : + * ml2 > ml1, and + * ip1+3 <= ip2 (usually < ip1+ml1) */ + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + int new_ml = ml; + if (new_ml > OPTIMAL_ML) new_ml = OPTIMAL_ML; + if (ip+new_ml > start2 + ml2 - MINMATCH) new_ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = new_ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } + /* Now, we have start2 = ip+new_ml, with new_ml = min(ml, OPTIMAL_ML=18) */ + + if (start2 + ml2 <= mflimit) { + ml3 = LZ4HC_InsertAndGetWiderMatch(ctx, + start2 + ml2 - 3, start2, matchlimit, ml2, &ref3, &start3, + maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + } else { + ml3 = ml2; + } + + if (ml3 == ml2) { /* No better match => encode ML1 and ML2 */ + /* ip & ref are known; Now for ml */ + if (start2 < ip+ml) ml = (int)(start2 - ip); + /* Now, encode 2 sequences */ + optr = op; + if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + ip = start2; + optr = op; + if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml2, ref2, limit, oend)) goto _dest_overflow; + continue; + } + + if (start3 < ip+ml+3) { /* Not enough space for match 2 : remove it */ + if (start3 >= (ip+ml)) { /* can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 */ + if (start2 < ip+ml) { + int correction = (int)(ip+ml - start2); + start2 += correction; + ref2 += correction; + ml2 -= correction; + if (ml2 < MINMATCH) { + start2 = start3; + ref2 = ref3; + ml2 = ml3; + } + } + + optr = op; + if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + ip = start3; + ref = ref3; + ml = ml3; + + start0 = start2; + ref0 = ref2; + ml0 = ml2; + goto _Search2; + } + + start2 = start3; + ref2 = ref3; + ml2 = ml3; + goto _Search3; + } + + /* + * OK, now we have 3 ascending matches; + * let's write the first one ML1. + * ip & ref are known; Now decide ml. + */ + if (start2 < ip+ml) { + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + if (ml > OPTIMAL_ML) ml = OPTIMAL_ML; + if (ip + ml > start2 + ml2 - MINMATCH) ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } else { + ml = (int)(start2 - ip); + } + } + optr = op; + if (LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ref, limit, oend)) goto _dest_overflow; + + /* ML2 becomes ML1 */ + ip = start2; ref = ref2; ml = ml2; + + /* ML3 becomes ML2 */ + start2 = start3; ref2 = ref3; ml2 = ml3; + + /* let's find a new ML3 */ + goto _Search3; + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + litLength + lastRunSize; + if (limit == limitedDestSize) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; /* Check output limit */ + /* adapt lastRunSize to fill 'dest' */ + lastRunSize = (size_t)(oend - op) - 1; + litLength = (lastRunSize + 255 - RUN_MASK) / 255; + lastRunSize -= litLength; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) (((char*)op)-dest); + +_dest_overflow: + if (limit == limitedDestSize) { + op = optr; /* restore correct out pointer */ + goto _last_literals; + } + return 0; +} + + +static int LZ4HC_compress_optimal( LZ4HC_CCtx_internal* ctx, + const char* const source, char* dst, + int* srcSizePtr, int dstCapacity, + int const nbSearches, size_t sufficient_len, + const limitedOutput_directive limit, int const fullUpdate, + const dictCtx_directive dict, + HCfavor_e favorDecSpeed); + + +LZ4_FORCE_INLINE int LZ4HC_compress_generic_internal ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + typedef enum { lz4hc, lz4opt } lz4hc_strat_e; + typedef struct { + lz4hc_strat_e strat; + U32 nbSearches; + U32 targetLength; + } cParams_t; + static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { + { lz4hc, 2, 16 }, /* 0, unused */ + { lz4hc, 2, 16 }, /* 1, unused */ + { lz4hc, 2, 16 }, /* 2, unused */ + { lz4hc, 4, 16 }, /* 3 */ + { lz4hc, 8, 16 }, /* 4 */ + { lz4hc, 16, 16 }, /* 5 */ + { lz4hc, 32, 16 }, /* 6 */ + { lz4hc, 64, 16 }, /* 7 */ + { lz4hc, 128, 16 }, /* 8 */ + { lz4hc, 256, 16 }, /* 9 */ + { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ + { lz4opt, 512,128 }, /*11 */ + { lz4opt,16384,LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ + }; + + DEBUGLOG(4, "LZ4HC_compress_generic(%p, %p, %d)", ctx, src, *srcSizePtr); + + if (limit == limitedDestSize && dstCapacity < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ + + ctx->end += *srcSizePtr; + if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ + cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); + { cParams_t const cParam = clTable[cLevel]; + HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; + if (cParam.strat == lz4hc) + return LZ4HC_compress_hashChain(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, limit, dict); + assert(cParam.strat == lz4opt); + return LZ4HC_compress_optimal(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, cParam.targetLength, limit, + cLevel == LZ4HC_CLEVEL_MAX, /* ultra mode */ + dict, favor); + } +} + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock); + +static int LZ4HC_compress_generic_noDictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + assert(ctx->dictCtx == NULL); + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, noDictCtx); +} + +static int LZ4HC_compress_generic_dictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + const size_t position = ctx->end - ctx->base - ctx->lowLimit; + assert(ctx->dictCtx != NULL); + if (position >= 64 KB) { + ctx->dictCtx = NULL; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else if (position == 0 && *srcSizePtr > 4 KB) { + memcpy(ctx, ctx->dictCtx, sizeof(LZ4HC_CCtx_internal)); + LZ4HC_setExternalDict(ctx, (const BYTE *)src); + ctx->compressionLevel = (short)cLevel; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, usingDictCtx); + } +} + +static int LZ4HC_compress_generic ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + if (ctx->dictCtx == NULL) { + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_dictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } +} + + +int LZ4_sizeofStateHC(void) { return sizeof(LZ4_streamHC_t); } + +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; + if (((size_t)(state)&(sizeof(void*)-1)) != 0) return 0; /* Error : state is not aligned for pointers (32 or 64 bits) */ + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)state, compressionLevel); + LZ4HC_init (ctx, (const BYTE*)src); + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, limitedOutput); + else + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, noLimit); +} + +int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + if (((size_t)(state)&(sizeof(void*)-1)) != 0) return 0; /* Error : state is not aligned for pointers (32 or 64 bits) */ + LZ4_resetStreamHC ((LZ4_streamHC_t*)state, compressionLevel); + return LZ4_compress_HC_extStateHC_fastReset(state, src, dst, srcSize, dstCapacity, compressionLevel); +} + +int LZ4_compress_HC(const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); +#else + LZ4_streamHC_t state; + LZ4_streamHC_t* const statePtr = &state; +#endif + int const cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + free(statePtr); +#endif + return cSize; +} + +/* LZ4_compress_HC_destSize() : + * only compatible with regular HC parser */ +int LZ4_compress_HC_destSize(void* LZ4HC_Data, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) +{ + LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse; + LZ4_resetStreamHC((LZ4_streamHC_t*)LZ4HC_Data, cLevel); + LZ4HC_init(ctx, (const BYTE*) source); + return LZ4HC_compress_generic(ctx, source, dest, sourceSizePtr, targetDestSize, cLevel, limitedDestSize); +} + + + +/************************************** +* Streaming Functions +**************************************/ +/* allocation */ +LZ4_streamHC_t* LZ4_createStreamHC(void) { + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (LZ4_streamHCPtr==NULL) return NULL; + LZ4_resetStreamHC(LZ4_streamHCPtr, LZ4HC_CLEVEL_DEFAULT); + return LZ4_streamHCPtr; +} + +int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) { + DEBUGLOG(4, "LZ4_freeStreamHC(%p)", LZ4_streamHCPtr); + if (!LZ4_streamHCPtr) return 0; /* support free on NULL */ + free(LZ4_streamHCPtr); + return 0; +} + + +/* initialization */ +void LZ4_resetStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + LZ4_STATIC_ASSERT(sizeof(LZ4HC_CCtx_internal) <= sizeof(size_t) * LZ4_STREAMHCSIZE_SIZET); /* if compilation fails here, LZ4_STREAMHCSIZE must be increased */ + DEBUGLOG(4, "LZ4_resetStreamHC(%p, %d)", LZ4_streamHCPtr, compressionLevel); + LZ4_streamHCPtr->internal_donotuse.end = (const BYTE *)(ptrdiff_t)-1; + LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = 0; + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_resetStreamHC_fast (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(4, "LZ4_resetStreamHC_fast(%p, %d)", LZ4_streamHCPtr, compressionLevel); + LZ4_streamHCPtr->internal_donotuse.end -= (uptrval)LZ4_streamHCPtr->internal_donotuse.base; + LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + if (compressionLevel < 1) compressionLevel = LZ4HC_CLEVEL_DEFAULT; + if (compressionLevel > LZ4HC_CLEVEL_MAX) compressionLevel = LZ4HC_CLEVEL_MAX; + LZ4_streamHCPtr->internal_donotuse.compressionLevel = (short)compressionLevel; +} + +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor) +{ + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = (favor!=0); +} + +int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, const char* dictionary, int dictSize) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_loadDictHC(%p, %p, %d)", LZ4_streamHCPtr, dictionary, dictSize); + if (dictSize > 64 KB) { + dictionary += dictSize - 64 KB; + dictSize = 64 KB; + } + LZ4_resetStreamHC(LZ4_streamHCPtr, ctxPtr->compressionLevel); + LZ4HC_init (ctxPtr, (const BYTE*)dictionary); + ctxPtr->end = (const BYTE*)dictionary + dictSize; + if (dictSize >= 4) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); + return dictSize; +} + +void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream) { + working_stream->internal_donotuse.dictCtx = dictionary_stream != NULL ? &(dictionary_stream->internal_donotuse) : NULL; +} + +/* compression */ + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock) +{ + DEBUGLOG(4, "LZ4HC_setExternalDict(%p, %p)", ctxPtr, newBlock); + if (ctxPtr->end >= ctxPtr->base + ctxPtr->dictLimit + 4) + LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ + + /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictLimit = (U32)(ctxPtr->end - ctxPtr->base); + ctxPtr->dictBase = ctxPtr->base; + ctxPtr->base = newBlock - ctxPtr->dictLimit; + ctxPtr->end = newBlock; + ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ +} + +static int LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int dstCapacity, + limitedOutput_directive limit) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_compressHC_continue_generic(%p, %p, %d)", LZ4_streamHCPtr, src, *srcSizePtr); + /* auto-init if forgotten */ + if (ctxPtr->base == NULL) LZ4HC_init (ctxPtr, (const BYTE*) src); + + /* Check overflow */ + if ((size_t)(ctxPtr->end - ctxPtr->base) > 2 GB) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->base) - ctxPtr->dictLimit; + if (dictSize > 64 KB) dictSize = 64 KB; + LZ4_loadDictHC(LZ4_streamHCPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize); + } + + /* Check if blocks follow each other */ + if ((const BYTE*)src != ctxPtr->end) LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) src + *srcSizePtr; + const BYTE* const dictBegin = ctxPtr->dictBase + ctxPtr->lowLimit; + const BYTE* const dictEnd = ctxPtr->dictBase + ctxPtr->dictLimit; + if ((sourceEnd > dictBegin) && ((const BYTE*)src < dictEnd)) { + if (sourceEnd > dictEnd) sourceEnd = dictEnd; + ctxPtr->lowLimit = (U32)(sourceEnd - ctxPtr->dictBase); + if (ctxPtr->dictLimit - ctxPtr->lowLimit < 4) ctxPtr->lowLimit = ctxPtr->dictLimit; + } + } + + return LZ4HC_compress_generic (ctxPtr, src, dst, srcSizePtr, dstCapacity, ctxPtr->compressionLevel, limit); +} + +int LZ4_compress_HC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int srcSize, int dstCapacity) +{ + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, limitedOutput); + else + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, noLimit); +} + +int LZ4_compress_HC_continue_destSize (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int* srcSizePtr, int targetDestSize) +{ + return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, limitedDestSize); +} + + + +/* dictionary saving */ + +int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize) +{ + LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse; + int const prefixSize = (int)(streamPtr->end - (streamPtr->base + streamPtr->dictLimit)); + DEBUGLOG(4, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize); + if (dictSize > 64 KB) dictSize = 64 KB; + if (dictSize < 4) dictSize = 0; + if (dictSize > prefixSize) dictSize = prefixSize; + memmove(safeBuffer, streamPtr->end - dictSize, dictSize); + { U32 const endIndex = (U32)(streamPtr->end - streamPtr->base); + streamPtr->end = (const BYTE*)safeBuffer + dictSize; + streamPtr->base = streamPtr->end - endIndex; + streamPtr->dictLimit = endIndex - dictSize; + streamPtr->lowLimit = endIndex - dictSize; + if (streamPtr->nextToUpdate < streamPtr->dictLimit) streamPtr->nextToUpdate = streamPtr->dictLimit; + } + return dictSize; +} + + +/*********************************** +* Deprecated Functions +***********************************/ +/* These functions currently generate deprecation warnings */ +/* Deprecated compression functions */ +int LZ4_compressHC(const char* src, char* dst, int srcSize) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2(const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_withStateHC (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2_withStateHC (void* state, const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, LZ4_compressBound(srcSize)); } +int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, maxDstSize); } + + +/* Deprecated streaming functions */ +int LZ4_sizeofStreamStateHC(void) { return LZ4_STREAMHCSIZE; } + +int LZ4_resetStreamStateHC(void* state, char* inputBuffer) +{ + LZ4HC_CCtx_internal *ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; + if ((((size_t)state) & (sizeof(void*)-1)) != 0) return 1; /* Error : pointer is not aligned for pointer (32 or 64 bits) */ + LZ4_resetStreamHC((LZ4_streamHC_t*)state, ((LZ4_streamHC_t*)state)->internal_donotuse.compressionLevel); + LZ4HC_init(ctx, (const BYTE*)inputBuffer); + return 0; +} + +void* LZ4_createHC (const char* inputBuffer) +{ + LZ4_streamHC_t* hc4 = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (hc4 == NULL) return NULL; /* not enough memory */ + LZ4_resetStreamHC(hc4, 0 /* compressionLevel */); + LZ4HC_init (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return hc4; +} + +int LZ4_freeHC (void* LZ4HC_Data) { + if (!LZ4HC_Data) return 0; /* support free on NULL */ + FREEMEM(LZ4HC_Data); + return 0; +} + +int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, noLimit); +} + +int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int dstCapacity, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, dstCapacity, cLevel, limitedOutput); +} + +char* LZ4_slideInputBufferHC(void* LZ4HC_Data) +{ + LZ4_streamHC_t *ctx = (LZ4_streamHC_t*)LZ4HC_Data; + const BYTE *bufferStart = ctx->internal_donotuse.base + ctx->internal_donotuse.lowLimit; + LZ4_resetStreamHC_fast(ctx, ctx->internal_donotuse.compressionLevel); + /* avoid const char * -> char * conversion warning :( */ + return (char *)(uptrval)bufferStart; +} + + +/* ================================================ + * LZ4 Optimal parser (levels 10-12) + * ===============================================*/ +typedef struct { + int price; + int off; + int mlen; + int litlen; +} LZ4HC_optimal_t; + +/* price in bytes */ +LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) +{ + int price = litlen; + if (litlen >= (int)RUN_MASK) + price += 1 + (litlen-RUN_MASK)/255; + return price; +} + + +/* requires mlen >= MINMATCH */ +LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) +{ + int price = 1 + 2 ; /* token + 16-bit offset */ + + price += LZ4HC_literalsPrice(litlen); + + if (mlen >= (int)(ML_MASK+MINMATCH)) + price += 1 + (mlen-(ML_MASK+MINMATCH))/255; + + return price; +} + + +typedef struct { + int off; + int len; +} LZ4HC_match_t; + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, + const BYTE* ip, const BYTE* const iHighLimit, + int minLen, int nbSearches, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + LZ4HC_match_t match = { 0 , 0 }; + const BYTE* matchPtr = NULL; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + int matchLength = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &matchPtr, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + if (matchLength <= minLen) return match; + if (favorDecSpeed) { + if ((matchLength>18) & (matchLength<=36)) matchLength=18; /* favor shortcut */ + } + match.len = matchLength; + match.off = (int)(ip-matchPtr); + return match; +} + + +static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, + const char* const source, + char* dst, + int* srcSizePtr, + int dstCapacity, + int const nbSearches, + size_t sufficient_len, + const limitedOutput_directive limit, + int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ +#define TRAILING_LITERALS 3 + LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* ~64 KB, which is a bit large for stack... */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + BYTE* op = (BYTE*) dst; + BYTE* opSaved = (BYTE*) dst; + BYTE* oend = op + dstCapacity; + + /* init */ + DEBUGLOG(5, "LZ4HC_compress_optimal"); + *srcSizePtr = 0; + if (limit == limitedDestSize) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; + + /* Main Loop */ + assert(ip - anchor < LZ4_MAX_INPUT_SIZE); + while (ip <= mflimit) { + int const llen = (int)(ip - anchor); + int best_mlen, best_off; + int cur, last_match_pos = 0; + + LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + if (firstMatch.len==0) { ip++; continue; } + + if ((size_t)firstMatch.len > sufficient_len) { + /* good enough solution : immediate encoding */ + int const firstML = firstMatch.len; + const BYTE* const matchPos = ip - firstMatch.off; + opSaved = op; + if ( LZ4HC_encodeSequence(&ip, &op, &anchor, firstML, matchPos, limit, oend) ) /* updates ip, op and anchor */ + goto _dest_overflow; + continue; + } + + /* set prices for first positions (literals) */ + { int rPos; + for (rPos = 0 ; rPos < MINMATCH ; rPos++) { + int const cost = LZ4HC_literalsPrice(llen + rPos); + opt[rPos].mlen = 1; + opt[rPos].off = 0; + opt[rPos].litlen = llen + rPos; + opt[rPos].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + rPos, cost, opt[rPos].litlen); + } } + /* set prices using initial match */ + { int mlen = MINMATCH; + int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + int const offset = firstMatch.off; + assert(matchML < LZ4_OPT_NUM); + for ( ; mlen <= matchML ; mlen++) { + int const cost = LZ4HC_sequencePrice(llen, mlen); + opt[mlen].mlen = mlen; + opt[mlen].off = offset; + opt[mlen].litlen = llen; + opt[mlen].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", + mlen, cost, mlen); + } } + last_match_pos = firstMatch.len; + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + + /* check further positions */ + for (cur = 1; cur < last_match_pos; cur++) { + const BYTE* const curPtr = ip + cur; + LZ4HC_match_t newMatch; + + if (curPtr > mflimit) break; + DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", + cur, opt[cur].price, opt[cur+1].price, cur+1); + if (fullUpdate) { + /* not useful to search here if next position has same (or lower) cost */ + if ( (opt[cur+1].price <= opt[cur].price) + /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ + && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) + continue; + } else { + /* not useful to search here if next position has same (or lower) cost */ + if (opt[cur+1].price <= opt[cur].price) continue; + } + + DEBUGLOG(7, "search at rPos:%u", cur); + if (fullUpdate) + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + else + /* only test matches of minimum length; slightly faster, but misses a few bytes */ + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches, dict, favorDecSpeed); + if (!newMatch.len) continue; + + if ( ((size_t)newMatch.len > sufficient_len) + || (newMatch.len + cur >= LZ4_OPT_NUM) ) { + /* immediate encoding */ + best_mlen = newMatch.len; + best_off = newMatch.off; + last_match_pos = cur + 1; + goto encode; + } + + /* before match : set price with literals at beginning */ + { int const baseLitlen = opt[cur].litlen; + int litlen; + for (litlen = 1; litlen < MINMATCH; litlen++) { + int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); + int const pos = cur + litlen; + if (price < opt[pos].price) { + opt[pos].mlen = 1; /* literal */ + opt[pos].off = 0; + opt[pos].litlen = baseLitlen+litlen; + opt[pos].price = price; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", + pos, price, opt[pos].litlen); + } } } + + /* set prices using match at position = cur */ + { int const matchML = newMatch.len; + int ml = MINMATCH; + + assert(cur + newMatch.len < LZ4_OPT_NUM); + for ( ; ml <= matchML ; ml++) { + int const pos = cur + ml; + int const offset = newMatch.off; + int price; + int ll; + DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", + pos, last_match_pos); + if (opt[cur].mlen == 1) { + ll = opt[cur].litlen; + price = ((cur > ll) ? opt[cur - ll].price : 0) + + LZ4HC_sequencePrice(ll, ml); + } else { + ll = 0; + price = opt[cur].price + LZ4HC_sequencePrice(0, ml); + } + + assert((U32)favorDecSpeed <= 1); + if (pos > last_match_pos+TRAILING_LITERALS + || price <= opt[pos].price - (int)favorDecSpeed) { + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", + pos, price, ml); + assert(pos < LZ4_OPT_NUM); + if ( (ml == matchML) /* last pos of last match */ + && (last_match_pos < pos) ) + last_match_pos = pos; + opt[pos].mlen = ml; + opt[pos].off = offset; + opt[pos].litlen = ll; + opt[pos].price = price; + } } } + /* complete following positions with literals */ + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + } /* for (cur = 1; cur <= last_match_pos; cur++) */ + + best_mlen = opt[last_match_pos].mlen; + best_off = opt[last_match_pos].off; + cur = last_match_pos - best_mlen; + + encode: /* cur, last_match_pos, best_mlen, best_off must be set */ + assert(cur < LZ4_OPT_NUM); + assert(last_match_pos >= 1); /* == 1 when only one candidate */ + DEBUGLOG(6, "reverse traversal, looking for shortest path (last_match_pos=%i)", last_match_pos); + { int candidate_pos = cur; + int selected_matchLength = best_mlen; + int selected_offset = best_off; + while (1) { /* from end to beginning */ + int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ + int const next_offset = opt[candidate_pos].off; + DEBUGLOG(7, "pos %i: sequence length %i", candidate_pos, selected_matchLength); + opt[candidate_pos].mlen = selected_matchLength; + opt[candidate_pos].off = selected_offset; + selected_matchLength = next_matchLength; + selected_offset = next_offset; + if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ + assert(next_matchLength > 0); /* can be 1, means literal */ + candidate_pos -= next_matchLength; + } } + + /* encode all recorded sequences in order */ + { int rPos = 0; /* relative position (to ip) */ + while (rPos < last_match_pos) { + int const ml = opt[rPos].mlen; + int const offset = opt[rPos].off; + if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ + rPos += ml; + assert(ml >= MINMATCH); + assert((offset >= 1) && (offset <= MAX_DISTANCE)); + opSaved = op; + if ( LZ4HC_encodeSequence(&ip, &op, &anchor, ml, ip - offset, limit, oend) ) /* updates ip, op and anchor */ + goto _dest_overflow; + } } + } /* while (ip <= mflimit) */ + + _last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t litLength = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + litLength + lastRunSize; + if (limit == limitedDestSize) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; /* Check output limit */ + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (size_t)(oend - op) - 1; + litLength = (lastRunSize + 255 - RUN_MASK) / 255; + lastRunSize -= litLength; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) ((char*)op-dst); + + _dest_overflow: + if (limit == limitedDestSize) { + op = opSaved; /* restore correct out pointer */ + goto _last_literals; + } + return 0; + } diff --git a/third-party/lz4/lz4hc.h b/third-party/lz4/lz4hc.h new file mode 100644 index 000000000..bb5e07373 --- /dev/null +++ b/third-party/lz4/lz4hc.h @@ -0,0 +1,347 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2017, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 3 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the more powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* state, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * Existing states can be re-used several times, using LZ4_resetStreamHC(). + * These methods are API and ABI stable, they can be used in combination with a DLL. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +LZ4LIB_API void LZ4_resetStreamHC (LZ4_streamHC_t* streamHCPtr, int compressionLevel); +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, const char* src, char* dst, int srcSize, int maxDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + +/* + These functions compress data in successive blocks of any size, using previous blocks as dictionary. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring buffers scenario is automatically detected and handled by LZ4_compress_HC_continue(). + + Before starting compression, state must be properly initialized, using LZ4_resetStreamHC(). + A first "fictional block" can then be designated as initial dictionary, using LZ4_loadDictHC() (Optional). + + Then, use LZ4_compress_HC_continue() to compress each successive block. + Previous memory blocks (including initial dictionary when present) must remain accessible and unmodified during compression. + 'dst' buffer should be sized to handle worst case scenarios (see LZ4_compressBound()), to ensure operation success. + Because in case of failure, the API does not guarantee context recovery, and context will have to be reset. + If `dst` buffer budget cannot be >= LZ4_compressBound(), consider using LZ4_compress_HC_continue_destSize() instead. + + If, for any reason, previous data block can't be preserved unmodified in memory for next compression block, + you can save it to a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer'. +*/ + + +/*-************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions. + * They are exposed to allow static allocation of `LZ4_streamHC_t`. + * Using these definitions makes the code vulnerable to potential API break when upgrading LZ4 + ****************************************************************/ +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= 199901L) /* C99 */) +#include + +typedef struct LZ4HC_CCtx_internal LZ4HC_CCtx_internal; +struct LZ4HC_CCtx_internal +{ + uint32_t hashTable[LZ4HC_HASHTABLESIZE]; + uint16_t chainTable[LZ4HC_MAXD]; + const uint8_t* end; /* next block here to continue on current prefix */ + const uint8_t* base; /* All index relative to this position */ + const uint8_t* dictBase; /* alternate base for extDict */ + uint32_t dictLimit; /* below that point, need extDict */ + uint32_t lowLimit; /* below that point, no more dict */ + uint32_t nextToUpdate; /* index from which to continue dictionary update */ + short compressionLevel; + short favorDecSpeed; + const LZ4HC_CCtx_internal* dictCtx; +}; + +#else + +typedef struct LZ4HC_CCtx_internal LZ4HC_CCtx_internal; +struct LZ4HC_CCtx_internal +{ + unsigned int hashTable[LZ4HC_HASHTABLESIZE]; + unsigned short chainTable[LZ4HC_MAXD]; + const unsigned char* end; /* next block here to continue on current prefix */ + const unsigned char* base; /* All index relative to this position */ + const unsigned char* dictBase; /* alternate base for extDict */ + unsigned int dictLimit; /* below that point, need extDict */ + unsigned int lowLimit; /* below that point, no more dict */ + unsigned int nextToUpdate; /* index from which to continue dictionary update */ + short compressionLevel; + short favorDecSpeed; + const LZ4HC_CCtx_internal* dictCtx; +}; + +#endif + +#define LZ4_STREAMHCSIZE (4*LZ4HC_HASHTABLESIZE + 2*LZ4HC_MAXD + 56) /* 262200 */ +#define LZ4_STREAMHCSIZE_SIZET (LZ4_STREAMHCSIZE / sizeof(size_t)) +union LZ4_streamHC_u { + size_t table[LZ4_STREAMHCSIZE_SIZET]; + LZ4HC_CCtx_internal internal_donotuse; +}; /* previously typedef'd to LZ4_streamHC_t */ +/* + LZ4_streamHC_t : + This structure allows static allocation of LZ4 HC streaming state. + State must be initialized using LZ4_resetStreamHC() before first use. + + Static allocation shall only be used in combination with static linking. + When invoking LZ4 from a DLL, use create/free functions instead, which are API and ABI stable. +*/ + + +/*-************************************ +* Deprecated Functions +**************************************/ +/* see lz4.h LZ4_DISABLE_DEPRECATE_WARNINGS to turn off deprecation warnings */ + +/* deprecated compression functions */ +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC (const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2 (const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput (const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_withStateHC (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_withStateHC (void* state, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_extStateHC() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_withStateHC(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, use of + * LZ4_slideInputBufferHC() will truncate the history of the stream, rather + * than preserve a window-sized chunk of history. + */ +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API void* LZ4_createHC (const char* inputBuffer); +LZ4_DEPRECATED("use LZ4_saveDictHC() instead") LZ4LIB_API char* LZ4_slideInputBufferHC (void* LZ4HC_Data); +LZ4_DEPRECATED("use LZ4_freeStreamHC() instead") LZ4LIB_API int LZ4_freeHC (void* LZ4HC_Data); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_compress_HC_continue() instead") LZ4LIB_API int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel); +LZ4_DEPRECATED("use LZ4_createStreamHC() instead") LZ4LIB_API int LZ4_sizeofStreamStateHC(void); +LZ4_DEPRECATED("use LZ4_resetStreamHC() instead") LZ4LIB_API int LZ4_resetStreamStateHC(void* state, char* inputBuffer); + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_H_19834876238432 */ + + +/*-************************************************** + * !!!!! STATIC LINKING ONLY !!!!! + * Following definitions are considered experimental. + * They should not be linked from DLL, + * as there is no guarantee of API stability yet. + * Prototypes will be promoted to "stable" status + * after successfull usage in real-life scenarios. + ***************************************************/ +#ifdef LZ4_HC_STATIC_LINKING_ONLY /* protection macro */ +#ifndef LZ4_HC_SLO_098092834 +#define LZ4_HC_SLO_098092834 + +/*! LZ4_compress_HC_destSize() : v1.8.0 (experimental) + * Will try to compress as much data from `src` as possible + * that can fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + * `srcSizePtr` : value will be updated to indicate how much bytes were read from `src` + */ +int LZ4_compress_HC_destSize(void* LZ4HC_Data, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + +/*! LZ4_compress_HC_continue_destSize() : v1.8.0 (experimental) + * Similar as LZ4_compress_HC_continue(), + * but will read a variable nb of bytes from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + * `srcSizePtr` : value will be updated to indicate how much bytes were read from `src`. + */ +int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +/*! LZ4_setCompressionLevel() : v1.8.0 (experimental) + * It's possible to change compression level between 2 invocations of LZ4_compress_HC_continue*() + */ +void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_favorDecompressionSpeed() : v1.8.2 (experimental) + * Parser will select decisions favoring decompression over compression ratio. + * Only work at highest compression settings (level >= LZ4HC_CLEVEL_OPT_MIN) + */ +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + */ +void LZ4_resetStreamHC_fast(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + +/*! LZ4_attach_HC_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_API void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream); + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */