+
+/* Greedy parsing */
+static u32
+xpress_choose_items_greedy(struct xpress_compressor *c)
+{
+ struct lz_mf *mf;
+ u32 len_3_too_far;
+ const u8 *window_ptr;
+ const u8 *window_end;
+ struct lz_match matches[min(c->params.nice_match_length, c->params.max_search_depth)];
+ u32 num_matches;
+ struct xpress_item *next_chosen_item;
+
+ mf = c->mf;
+
+ lz_mf_load_window(mf, c->cur_window, c->cur_window_size);
+
+ if (c->cur_window_size <= 8192)
+ len_3_too_far = 2048;
+ else
+ len_3_too_far = 4096;
+
+ memset(c->freqs, 0, sizeof(c->freqs));
+
+ window_ptr = c->cur_window;
+ window_end = c->cur_window + c->cur_window_size;
+ next_chosen_item = c->chosen_items;
+
+ do {
+ /* Get longest match at the current position. */
+ num_matches = lz_mf_get_matches(mf, matches);
+
+ if (num_matches == 0 ||
+ (matches[num_matches - 1].len == 3 &&
+ matches[num_matches - 1].offset >= len_3_too_far))
+ {
+ *next_chosen_item++ = xpress_tally_literal(*window_ptr, c->freqs);
+ window_ptr += 1;
+ } else {
+ u32 len = matches[num_matches - 1].len;
+ u32 offset = matches[num_matches - 1].offset;
+
+ *next_chosen_item++ = xpress_tally_match(len, offset, c->freqs);
+ lz_mf_skip_positions(mf, len - 1);
+ window_ptr += len;
+ }
+ } while (window_ptr != window_end);
+
+ c->freqs[XPRESS_END_OF_DATA]++;
+ xpress_make_huffman_code(c);
+ return next_chosen_item - c->chosen_items;
+}
+
+/* Huffman-only parsing */
+static u32
+xpress_choose_items_huffonly(struct xpress_compressor *c)
+{
+ const u8 *window_ptr;
+ const u8 *window_end;
+ struct xpress_item *next_chosen_item;
+
+ memset(c->freqs, 0, sizeof(c->freqs));
+
+ window_ptr = c->cur_window;
+ window_end = c->cur_window + c->cur_window_size;
+ next_chosen_item = c->chosen_items;
+
+ do {
+ *next_chosen_item++ = xpress_tally_literal(*window_ptr++, c->freqs);
+ } while (window_ptr != window_end);
+
+ c->freqs[XPRESS_END_OF_DATA]++;
+ xpress_make_huffman_code(c);
+ return next_chosen_item - c->chosen_items;
+}
+
+/* Given the specified compression level and maximum window size, build the
+ * parameters to use for XPRESS compression. */
+static void
+xpress_build_params(unsigned int compression_level, u32 max_window_size,
+ struct xpress_compressor_params *xpress_params)
+{
+ memset(xpress_params, 0, sizeof(*xpress_params));
+
+ if (compression_level == 1) {
+
+ /* Huffman only (no Lempel-Ziv matches) */
+ xpress_params->mf_algo = LZ_MF_NULL;
+ xpress_params->choose_items_func = xpress_choose_items_huffonly;
+
+ } else if (compression_level < 30) {
+
+ /* Greedy parsing */
+ xpress_params->mf_algo = LZ_MF_HASH_CHAINS;
+ xpress_params->choose_items_func = xpress_choose_items_greedy;
+ xpress_params->nice_match_length = compression_level;
+ xpress_params->max_search_depth = compression_level / 2;
+
+ } else if (compression_level < 60) {
+
+ /* Lazy parsing */
+ xpress_params->mf_algo = LZ_MF_HASH_CHAINS;
+ xpress_params->choose_items_func = xpress_choose_items_lazy;
+ xpress_params->nice_match_length = compression_level;
+ xpress_params->max_search_depth = compression_level / 2;
+
+ } else {
+
+ /* Near-optimal parsing */
+ xpress_params->choose_items_func = xpress_choose_items_near_optimal;
+ if (max_window_size >= 16384)
+ xpress_params->mf_algo = LZ_MF_BINARY_TREES;
+ else
+ xpress_params->mf_algo = LZ_MF_HASH_CHAINS;
+ xpress_params->num_optim_passes = compression_level / 40;
+ xpress_params->nice_match_length = min(compression_level / 2,
+ XPRESS_MAX_MATCH_LEN);
+ xpress_params->max_search_depth = min(compression_level,
+ XPRESS_MAX_MATCH_LEN);
+ }
+}
+
+/* Given the specified XPRESS parameters and maximum window size, build the
+ * parameters to use for match-finding. */
+static void
+xpress_build_mf_params(const struct xpress_compressor_params *xpress_params,
+ u32 max_window_size, struct lz_mf_params *mf_params)
+{
+ memset(mf_params, 0, sizeof(*mf_params));
+
+ mf_params->algorithm = xpress_params->mf_algo;
+ mf_params->max_window_size = max_window_size;
+ mf_params->min_match_len = XPRESS_MIN_MATCH_LEN;
+ mf_params->max_match_len = XPRESS_MAX_MATCH_LEN;
+ mf_params->max_search_depth = xpress_params->max_search_depth;
+ mf_params->nice_match_len = xpress_params->nice_match_length;
+}
+
+static void
+xpress_free_compressor(void *_c);
+
+static u64
+xpress_get_needed_memory(size_t max_window_size, unsigned int compression_level)
+{
+ u64 size = 0;
+ struct xpress_compressor_params params;
+
+ if (max_window_size > XPRESS_MAX_OFFSET + 1)
+ return 0;
+
+ xpress_build_params(compression_level, max_window_size, ¶ms);
+
+ size += sizeof(struct xpress_compressor);
+
+ size += lz_mf_get_needed_memory(params.mf_algo, max_window_size);
+
+ if (params.choose_items_func == xpress_choose_items_near_optimal) {
+ size += (XPRESS_OPTIM_ARRAY_LENGTH + params.nice_match_length) *
+ sizeof(struct xpress_mc_pos_data);
+ if (params.num_optim_passes > 1) {
+ size_t cache_len = max(max_window_size * XPRESS_CACHE_PER_POS,
+ params.max_search_depth + 1);
+ size += cache_len * sizeof(struct lz_match);
+ } else {
+ size += params.max_search_depth * sizeof(struct lz_match);
+ }
+ }
+
+ size += max_window_size * sizeof(struct xpress_item);
+
+ return size;
+}
+
+static int
+xpress_create_compressor(size_t max_window_size, unsigned int compression_level,
+ void **c_ret)
+{
+ struct xpress_compressor *c;
+ struct xpress_compressor_params params;
+ struct lz_mf_params mf_params;
+
+ if (max_window_size > XPRESS_MAX_OFFSET + 1)
+ return WIMLIB_ERR_INVALID_PARAM;
+
+ xpress_build_params(compression_level, max_window_size, ¶ms);
+ xpress_build_mf_params(¶ms, max_window_size, &mf_params);
+
+ c = CALLOC(1, sizeof(struct xpress_compressor));
+ if (!c)
+ goto oom;
+
+ c->params = params;
+
+ c->mf = lz_mf_alloc(&mf_params);
+ if (!c->mf)
+ goto oom;
+
+ if (params.choose_items_func == xpress_choose_items_near_optimal) {
+ c->optimum = MALLOC((XPRESS_OPTIM_ARRAY_LENGTH +
+ params.nice_match_length) *
+ sizeof(struct xpress_mc_pos_data));
+ if (!c->optimum)
+ goto oom;
+ if (params.num_optim_passes > 1) {
+ size_t cache_len = max(max_window_size * XPRESS_CACHE_PER_POS,
+ params.max_search_depth + 1);
+ c->cached_matches = MALLOC(cache_len * sizeof(struct lz_match));
+ if (!c->cached_matches)
+ goto oom;
+ c->cache_limit = c->cached_matches + cache_len -
+ (params.max_search_depth + 1);
+ } else {
+ c->cached_matches = MALLOC(params.max_search_depth *
+ sizeof(struct lz_match));
+ if (!c->cached_matches)
+ goto oom;
+ }
+ }
+
+ c->chosen_items = MALLOC(max_window_size * sizeof(struct xpress_item));
+ if (!c->chosen_items)
+ goto oom;
+
+ *c_ret = c;
+ return 0;
+
+oom:
+ xpress_free_compressor(c);
+ return WIMLIB_ERR_NOMEM;
+}
+
+static size_t
+xpress_compress(const void *uncompressed_data, size_t uncompressed_size,
+ void *compressed_data, size_t compressed_size_avail, void *_c)
+{
+ struct xpress_compressor *c = _c;
+ u32 num_chosen_items;
+ u8 *cptr;
+ struct xpress_output_bitstream os;
+ u32 compressed_size;
+
+ /* XPRESS requires 256 bytes of overhead for the Huffman code, so it's
+ * impossible to compress 256 bytes or less of data to less than the
+ * input size. */
+ if (compressed_size_avail < XPRESS_NUM_SYMBOLS / 2 + 50)
+ return 0;
+
+ /* Determine match/literal sequence to divide the data into. */
+ c->cur_window = uncompressed_data;
+ c->cur_window_size = uncompressed_size;
+ num_chosen_items = (*c->params.choose_items_func)(c);
+
+ /* Output the Huffman code as a series of 512 4-bit lengths. */
+ cptr = compressed_data;
+ for (unsigned i = 0; i < XPRESS_NUM_SYMBOLS; i += 2)
+ *cptr++ = (c->lens[i] & 0xf) | (c->lens[i + 1] << 4);
+
+ /* Output the encoded matches/literals. */
+ xpress_init_output(&os, cptr,
+ compressed_size_avail - XPRESS_NUM_SYMBOLS / 2);
+ xpress_write_items(&os, c->chosen_items, num_chosen_items,
+ c->codewords, c->lens);
+
+ /* Flush any pending data and get the length of the compressed data. */
+ compressed_size = xpress_flush_output(&os);
+ if (compressed_size == 0)
+ return 0;
+
+ /* Return the length of the compressed data. */
+ return compressed_size + XPRESS_NUM_SYMBOLS / 2;
+}
+
+static void
+xpress_free_compressor(void *_c)
+{
+ struct xpress_compressor *c = _c;
+
+ if (c) {
+ lz_mf_free(c->mf);
+ FREE(c->cached_matches);
+ FREE(c->optimum);
+ FREE(c->chosen_items);
+ FREE(c);
+ }
+}
+
+const struct compressor_ops xpress_compressor_ops = {
+ .get_needed_memory = xpress_get_needed_memory,
+ .create_compressor = xpress_create_compressor,
+ .compress = xpress_compress,
+ .free_compressor = xpress_free_compressor,
+};