// The encoder's parameters for each level, transcribed from the reference
// implementation: facebook/zstd v1.5.7, lib/compress/clevels.h,
// `ZSTD_defaultCParameters` (BSD license; see NOTICE), levels 1 to 12 of
// its four tables. Tree rows use lazy2 with the same adjusted numbers:
// binary trees and optimal parsing are outside this encoder's scope.
///|
/// The match-finding strategy of a level, as `ZSTD_strategy` names it.
/// `Btlazy2` and `Btopt` appear in the tables for small inputs at levels 9
/// to 12; the encoder maps these to Lazy2, retaining their numeric row.
priv enum Strategy {
Fast
Dfast
Greedy
Lazy
Lazy2
Btlazy2
Btopt
}
///|
/// One row of `ZSTD_defaultCParameters`: the logs of the window, the chain
/// table and the hash table, the search depth (log), the shortest match,
/// the target length and the strategy. Fast reads window_log, hash_log,
/// min_match (hash width) and target_length (acceleration).
/// Double-fast additionally uses chain_log for its short hash table and
/// always hashes eight bytes in its long table.
priv struct Params {
window_log : Int32
chain_log : Int32
hash_log : Int32
search_log : Int32
min_match : Int32
target_length : Int32
strategy : Strategy
// Whether greedy, lazy and lazy2 search rows of tags instead of hash
// chains; set by `encoder_params`.
row_finder : Bool
}
///|
/// The highest level implemented; higher ones act as this one.
const MAX_LEVEL : Int32 = 12
///|
/// The level `Encoder` and `compress` use by default, as zstd's.
const DEFAULT_LEVEL : Int32 = 3
///|
fn row(
window_log : Int32,
chain_log : Int32,
hash_log : Int32,
search_log : Int32,
min_match : Int32,
target_length : Int32,
strategy : Strategy,
) -> Params {
{
window_log,
chain_log,
hash_log,
search_log,
min_match,
target_length,
strategy,
row_finder: false,
}
}
///|
/// The four tables, each for levels 1 to 12 (index `level - 1`): for any
/// input larger than 256 KiB or of unknown size, and for inputs of at most
/// 256 KiB, 128 KiB and 16 KiB.
let level_tables : FixedArray[FixedArray[Params]] = [
[
row(19, 13, 14, 1, 7, 0, Fast),
row(20, 15, 16, 1, 6, 0, Fast),
row(21, 16, 17, 1, 5, 0, Dfast),
row(21, 18, 18, 1, 5, 0, Dfast),
row(21, 18, 19, 3, 5, 2, Greedy),
row(21, 18, 19, 3, 5, 4, Lazy),
row(21, 19, 20, 4, 5, 8, Lazy),
row(21, 19, 20, 4, 5, 16, Lazy2),
row(22, 20, 21, 4, 5, 16, Lazy2),
row(22, 21, 22, 5, 5, 16, Lazy2),
row(22, 21, 22, 6, 5, 16, Lazy2),
row(22, 22, 23, 6, 5, 32, Lazy2),
],
[
row(18, 13, 14, 1, 6, 0, Fast),
row(18, 14, 14, 1, 5, 0, Dfast),
row(18, 16, 16, 1, 4, 0, Dfast),
row(18, 16, 17, 3, 5, 2, Greedy),
row(18, 17, 18, 5, 5, 2, Greedy),
row(18, 18, 19, 3, 5, 4, Lazy),
row(18, 18, 19, 4, 4, 4, Lazy),
row(18, 18, 19, 4, 4, 8, Lazy2),
row(18, 18, 19, 5, 4, 8, Lazy2),
row(18, 18, 19, 6, 4, 8, Lazy2),
row(18, 18, 19, 5, 4, 12, Btlazy2),
row(18, 19, 19, 7, 4, 12, Btlazy2),
],
[
row(17, 12, 13, 1, 6, 0, Fast),
row(17, 13, 15, 1, 5, 0, Fast),
row(17, 15, 16, 2, 5, 0, Dfast),
row(17, 17, 17, 2, 4, 0, Dfast),
row(17, 16, 17, 3, 4, 2, Greedy),
row(17, 16, 17, 3, 4, 4, Lazy),
row(17, 16, 17, 3, 4, 8, Lazy2),
row(17, 16, 17, 4, 4, 8, Lazy2),
row(17, 16, 17, 5, 4, 8, Lazy2),
row(17, 16, 17, 6, 4, 8, Lazy2),
row(17, 17, 17, 5, 4, 8, Btlazy2),
row(17, 18, 17, 7, 4, 12, Btlazy2),
],
[
row(14, 14, 15, 1, 5, 0, Fast),
row(14, 14, 15, 1, 4, 0, Fast),
row(14, 14, 15, 2, 4, 0, Dfast),
row(14, 14, 14, 4, 4, 2, Greedy),
row(14, 14, 14, 3, 4, 4, Lazy),
row(14, 14, 14, 4, 4, 8, Lazy2),
row(14, 14, 14, 6, 4, 8, Lazy2),
row(14, 14, 14, 8, 4, 8, Lazy2),
row(14, 15, 14, 5, 4, 8, Btlazy2),
row(14, 15, 14, 9, 4, 8, Btlazy2),
row(14, 15, 14, 3, 4, 12, Btopt),
row(14, 15, 14, 4, 3, 24, Btopt),
],
]
///|
/// The parameters for `level` (already clamped to 1..=12) and an input of
/// `size` bytes, or of unknown size.
///
/// As `ZSTD_getCParams` does, a known size picks the table for its size
/// class, and the window and tables then shrink as `ZSTD_adjustCParams`
/// does (without a dictionary); an unknown size
/// takes the table for large inputs as it is. The size is known only when
/// the caller states it, so the parameters, and the output, never depend on
/// how the input is split.
fn params_for(level : Int32, size : Int64?) -> Params {
guard size is Some(size) else { return level_tables[0][level - 1] }
let table = (if size <= 256 * 1024 { 1 } else { 0 }) +
(if size <= 128 * 1024 { 1 } else { 0 }) +
(if size <= 16 * 1024 { 1 } else { 0 })
let params = level_tables[table][level - 1]
// The smallest log whose power of 2 holds `size`, at least 6 (the
// smallest hash log), as `ZSTD_adjustCParams_internal` computes it; sizes
// above 2^30 are not resized.
let window_log = if size <= 1L << 30 {
let size_log = if size < 64 { 6 } else { 64 - (size - 1).clz() }
params.window_log.min(size_log)
} else {
params.window_log
}
let bt_scale = match params.strategy {
Btlazy2 | Btopt => 1
_ => 0
}
let cycle_log = params.chain_log - bt_scale
// Reduce the tables BEFORE restoring the frame's minimum windowLog.
// Otherwise tiny inputs retain tables sized for 1 KiB instead of 64 B.
// Dictionary tagging is inapplicable, and the row-finder hash cap (28
// or more) cannot reduce any of the level 1..12 rows (at most 23).
{
..params,
window_log: window_log.max(10),
hash_log: params.hash_log.min(window_log + 1),
chain_log: params.chain_log - (cycle_log - window_log).max(0),
}
}
///|
/// Selects by the adjusted row's strategy, and for greedy, lazy and lazy2
/// whether to search rows (windows over 2^14 bytes). Tree rows retain every numeric
/// parameter and use Lazy2 because binary trees/optimal parsing are outside
/// the supported strategies. Adjustment precedes mapping so the reference
/// tree cycle bit still governs the table sizes.
fn encoder_params(level : Int32, size : Int64?) -> Params {
let params = params_for(level, size)
match params.strategy {
Btlazy2 | Btopt => { ..params, strategy: Lazy2, }
// As ZSTD_resolveRowMatchFinderMode decides by default, from the
// adjusted window. Tree rows keep hash chains: the reference searches
// them harder than a row's at most 64 candidates.
Greedy | Lazy | Lazy2 => { ..params, row_finder: params.window_log > 14, }
_ => params
}
}