/* * optenc.c * * Encoder for optimal parser * * * Future Improvements: * * When two estimations are equal, for example, "should I output a * character or a match?" there should be some way of deciding * which to take. Right now we force it to output a match, but * for text files, outputting a character results in a small * savings. Even when comparing two matches, we might want to * force it to take one type of match over another. */ #include "encoder.h" #define copymem(src,dst,size) memcpy(dst,src,size) static bool redo_first_block(t_encoder_context *context, long *bufpos_ptr); static void block_end(t_encoder_context *context, long BufPos); /* * encode a match of length (where >=2), and position */ #ifdef EXTRALONGMATCHES #define OUT_MATCH(len,pos) \ { \ ULONG enclen = (len); \ ULONG extlen = 0; \ if ( enclen > MAX_MATCH ) { \ extlen = enclen - MAX_MATCH; \ enclen = MAX_MATCH; \ } \ context->enc_ItemType [context->enc_literals >> 3] |= (1 << (context->enc_literals & 7)); \ context->enc_LitData [context->enc_literals ] = (byte)(enclen-MIN_MATCH); \ context->enc_ExtraLength[context->enc_literals++ ] = (ushort)(extlen); \ context->enc_DistData [context->enc_distances++ ] = (pos); \ } #else #define OUT_MATCH(len,pos) \ { \ context->enc_ItemType[(context->enc_literals >> 3)] |= (1 << (context->enc_literals & 7)); \ context->enc_LitData [context->enc_literals++] = (byte)(len-2); \ context->enc_DistData[context->enc_distances++] = pos; \ } #endif /* encode a character */ #define OUT_CHAR(ch) \ context->enc_LitData [context->enc_literals++] = ch; #define TREE_CREATE_CHECK() \ if (context->enc_literals >= context->enc_next_tree_create) \ { \ update_tree_estimates(context);\ context->enc_next_tree_create += TREE_CREATE_INTERVAL; \ } /* * Returns an estimation of how many bits it would take to output * a given character */ #define CHAR_EST(c) (numbits_t) (context->enc_main_tree_len[(c)]) /* * Returns an estimation of how many bits it would take to output * a given match. * * is the match length, where ml >= 2 * is the match position * * The result is stored in */ #define MATCH_EST(ml,mp,result) \ { \ ulong mp_slot; \ mp_slot = MP_SLOT(mp); \ if (ml < (NUM_PRIMARY_LENGTHS+2)) \ { \ result = (numbits_t) \ (context->enc_main_tree_len[(NUM_CHARS-2)+(mp_slot<enc_main_tree_len[(NUM_CHARS+NUM_PRIMARY_LENGTHS)+(mp_slot<enc_secondary_tree_len[ml-(NUM_PRIMARY_LENGTHS+2)] + \ enc_extra_bits[mp_slot]); \ } \ } #ifdef _DEBUG static void VERIFY_MATCH( t_encoder_context *context, long bufpos, int largest_match_len ) { int i, j; ulong match_pos; /* * Ensure match does not cross boundary */ _ASSERTE( largest_match_len <= (CHUNK_SIZE-1) - (bufpos & (CHUNK_SIZE-1)) ); for (i = MIN_MATCH; i <= largest_match_len; i++) { match_pos = context->enc_matchpos_table[i]; if (match_pos < NUM_REPEATED_OFFSETS) match_pos = context->enc_last_matchpos_offset[match_pos]; else match_pos -= (NUM_REPEATED_OFFSETS-1); _ASSERTE (match_pos <= context->enc_window_size-4); for (j = 0; j < i; j++) { _ASSERTE ( context->enc_MemWindow[bufpos+j] == context->enc_MemWindow[bufpos-match_pos+j] ); } } } #else #define VERIFY_MATCH(a,b,c) ; #endif void flush_all_pending_blocks(t_encoder_context *context) { /* * Force all blocks to be output */ while (context->enc_literals > 0) output_block(context); /* * Flush compressed data out to the caller */ perform_flush_output_callback(context); } void encoder_start(t_encoder_context *context) { long BytesRead, RealBufPos; /* * RealBufPos is our position in the window, * and equals [0...window_size + second_partition_size - 1] */ RealBufPos = context->enc_BufPos - (ulong)(context->enc_RealMemWindow - context->enc_MemWindow); BytesRead = comp_read_input(context, RealBufPos, CHUNK_SIZE); if (BytesRead > 0) opt_encode_top(context, BytesRead); } static void update_tree_estimates(t_encoder_context *context) { if (context->enc_literals) { /* * Get stats on literals from 0...context->enc_literals */ if (context->enc_need_to_recalc_stats) { /* * Cumulative total was destroyed, so need to * recalculate */ get_block_stats( context, 0, 0, context->enc_literals ); context->enc_need_to_recalc_stats = false; } else { /* * Add stats from last_literals...context->enc_literals * to cumulative total */ update_cumulative_block_stats( context, context->enc_last_literals, context->enc_last_distances, context->enc_literals ); } create_trees(context, false); /* don't generate codes */ fix_tree_cost_estimates(context); /* * For cumulative total */ context->enc_last_literals = context->enc_literals; context->enc_last_distances = context->enc_distances; } } void opt_encode_top(t_encoder_context *context, long BytesRead) { ulong BufPos; ulong RealBufPos; ulong BufPosEnd; ulong BufPosEndThisChunk; ulong MatchPos; ulong i; ulong end_pos; long EncMatchLength; /* must be a signed number */ long ExMatchOff = -1; /* initialize to prevent compiler warning */ /* * Current position in encoding window */ BufPos = context->enc_BufPos; /* * Stop encoding when we reach here */ BufPosEnd = context->enc_BufPos + BytesRead; /* * If this is our first time in here (since a new group), then * when we reach this many literals, update our tree cost * estimates. * * Also, output the file size we're using for translation * (0 means no translation at all, which will speed things up * for the decoder). */ if (context->enc_first_time_this_group) { context->enc_first_time_this_group = false; /* * Recreate trees when we reach this many literals */ context->enc_next_tree_create = 10000; if (context->enc_file_size_for_translation) { output_bits(context, 1, 1); /* translation */ output_bits(context, 16, context->enc_file_size_for_translation >> 16); output_bits(context, 16, context->enc_file_size_for_translation & 65535); } else { output_bits(context, 1, 0); /* no translation */ } } else { /* * If this is our second or later time in here, then add in the * strings we removed last time. * * We have to be careful here, though, because end_pos is * equal to our current BufPos - window_size, not * BufPos - i - window_size; we don't have that much history * around. */ for (i = BREAK_LENGTH; i > 0; i--) quick_insert_bsearch_findmatch( context, BufPos - (long) i, BufPos - context->enc_window_size+4 ); } while (1) { top_of_main_loop: /* * While we haven't reached the end of the data */ while (BufPos < BufPosEnd) { BufPosEndThisChunk = ( BufPos + CHUNK_SIZE ) & ~( CHUNK_SIZE - 1 ); if ( BufPosEndThisChunk > BufPosEnd ) { BufPosEndThisChunk = BufPosEnd; } /* * Search for matches of all different possible lengths, at BufPos */ EncMatchLength = binary_search_findmatch(context, BufPos); if (EncMatchLength < MIN_MATCH) { output_literal: /* * No match longer than 1 character exists in the history * window, so output the character at BufPos as a symbol. */ OUT_CHAR(context->enc_MemWindow[BufPos]); #ifdef TRACING EncTracingLiteral( BufPos, context->enc_MemWindow[BufPos] ); #endif BufPos++; /* * Check for exceeding literal buffer */ if (context->enc_literals >= (MAX_LITERAL_ITEMS-8)) block_end(context, BufPos); continue; } /* * Found a match. * * Make sure it cannot exceed the end of the buffer. */ if ( EncMatchLength > (long)( BufPosEndThisChunk - BufPos )) { EncMatchLength = (long)( BufPosEndThisChunk - BufPos ); /* * Oops, not enough for even a small match, so we * have to output a literal */ if (EncMatchLength < MIN_MATCH) goto output_literal; } VERIFY_MATCH(context, BufPos, EncMatchLength); if (EncMatchLength < FAST_DECISION_THRESHOLD) { /* * A match has been found that is between MIN_MATCH and * FAST_DECISION_THRESHOLD bytes in length. The following * algorithm is the optimal encoder that will determine the * most efficient order of matches and unmatched characters * over a span area defined by LOOK. * * The code is essentially a shortest path determination * algorithm. A stream of data can be encoded in a vast number * of different ways depending on the match lengths and offsets * chosen. The key to good compression ratios is to chose the * least expensive path. */ ulong span; ulong epos, bpos, NextPrevPos, MatchPos; decision_node *decision_node_ptr; long iterations; /* * Points to the end of the area covered by this match; the span * will continually be extended whenever we find more matches * later on. It will stop being extended when we reach a spot * where there are no matches, which is when we decide which * path to take to output the matches. */ span = BufPos + EncMatchLength; /* * The furthest position into which we will do our lookahead parsing */ epos = BufPos + LOOK; /* * Temporary BufPos variable */ bpos = BufPos; /* * Calculate the path to the next character if we output * an unmatched symbol. */ /* bits required to get here */ context->enc_decision_node[1].numbits = CHAR_EST(context->enc_MemWindow[BufPos]); /* where we came from */ context->enc_decision_node[1].path = BufPos; /* * For the match found, estimate the cost of encoding the match * for each possible match length, shortest offset combination. * * The cost, path and offset is stored at BufPos + Length. */ for (i = MIN_MATCH; i <= (ulong)EncMatchLength; i++) { /* * Get estimation of match cost given match length = i, * match position = context->enc_matchpos_table[i], and store * the result in context->enc_numbits[i] */ MATCH_EST(i, context->enc_matchpos_table[i], context->enc_decision_node[i].numbits); /* * Where we came from */ context->enc_decision_node[i].path = BufPos; /* * Associated match position with this path */ context->enc_decision_node[i].link = context->enc_matchpos_table[i]; #ifdef TRACING { ULONG TrMatchPos = context->enc_matchpos_table[i]; ULONG TrMatchOff; if ( TrMatchPos < NUM_REPEATED_OFFSETS ) { TrMatchOff = context->enc_last_matchpos_offset[ TrMatchPos ]; } else { TrMatchOff = TrMatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } context->enc_decision_node[i].matchoff = TrMatchOff; } #endif } /* * Set bit counter to zero at the start */ context->enc_decision_node[0].numbits = 0; /* * Initialise relative match position tables * * Really context->enc_repeated_offset_table[BufPos-bpos][x], but here * BufPos == bpos */ context->enc_decision_node[0].repeated_offset[0] = context->enc_last_matchpos_offset[0]; context->enc_decision_node[0].repeated_offset[1] = context->enc_last_matchpos_offset[1]; context->enc_decision_node[0].repeated_offset[2] = context->enc_last_matchpos_offset[2]; decision_node_ptr = &context->enc_decision_node[-(long) bpos]; #define rpt_offset_ptr(where,which_offset) decision_node_ptr[(where)].repeated_offset[(which_offset)] while (1) { numbits_t est, cum_numbits; BufPos++; /* * Set the proper repeated offset locations depending on the * shortest path to the location prior to searching for a * match. */ /* * If this is a match (i.e. path skips over more * than one character). */ if (decision_node_ptr[BufPos].path != (ulong) (BufPos-1)) { ulong LastPos = decision_node_ptr[BufPos].path; /* * link_ptr[BufPos] is the match position for this * location */ if (decision_node_ptr[BufPos].link >= NUM_REPEATED_OFFSETS) { context->enc_last_matchpos_offset[0] = decision_node_ptr[BufPos].link-(NUM_REPEATED_OFFSETS-1); context->enc_last_matchpos_offset[1] = rpt_offset_ptr(LastPos,0); context->enc_last_matchpos_offset[2] = rpt_offset_ptr(LastPos,1); } else if (decision_node_ptr[BufPos].link == 0) { context->enc_last_matchpos_offset[0] = rpt_offset_ptr(LastPos,0); context->enc_last_matchpos_offset[1] = rpt_offset_ptr(LastPos,1); context->enc_last_matchpos_offset[2] = rpt_offset_ptr(LastPos,2); } else if (decision_node_ptr[BufPos].link == 1) { context->enc_last_matchpos_offset[0] = rpt_offset_ptr(LastPos,1); context->enc_last_matchpos_offset[1] = rpt_offset_ptr(LastPos,0); context->enc_last_matchpos_offset[2] = rpt_offset_ptr(LastPos,2); } else /* == 2 */ { context->enc_last_matchpos_offset[0] = rpt_offset_ptr(LastPos,2); context->enc_last_matchpos_offset[1] = rpt_offset_ptr(LastPos,1); context->enc_last_matchpos_offset[2] = rpt_offset_ptr(LastPos,0); } } rpt_offset_ptr(BufPos,0) = context->enc_last_matchpos_offset[0]; rpt_offset_ptr(BufPos,1) = context->enc_last_matchpos_offset[1]; rpt_offset_ptr(BufPos,2) = context->enc_last_matchpos_offset[2]; /* * The following is one of the two possible break points from * the inner encoding loop. This break will exit the loop if * a point is reached that no match can incorporate; i.e. a * character that does not match back to anything is a point * where all possible paths will converge and the longest one * can be chosen. */ if (span == BufPos) break; /* * Search for matches at BufPos */ EncMatchLength = binary_search_findmatch(context, BufPos); /* * Make sure that the match does not exceed the stop point */ if ((ulong) EncMatchLength + BufPos > BufPosEndThisChunk) { EncMatchLength = BufPosEndThisChunk - BufPos; if (EncMatchLength < MIN_MATCH) EncMatchLength = 0; } VERIFY_MATCH(context, BufPos, EncMatchLength); /* * If the match is very long or it exceeds epos (either * surpassing the LOOK area, or exceeding past the end of the * input buffer), then break the loop and output the path. */ if (EncMatchLength > FAST_DECISION_THRESHOLD || BufPos + (ulong) EncMatchLength >= epos) { MatchPos = context->enc_matchpos_table[EncMatchLength]; #ifdef EXTRALONGMATCHES if ( EncMatchLength == MAX_MATCH ) { if ( MatchPos < NUM_REPEATED_OFFSETS ) { ExMatchOff = context->enc_last_matchpos_offset[ MatchPos ]; } else { ExMatchOff = MatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } } #endif #ifdef TRACING { ULONG TrMatchOff; if ( MatchPos < NUM_REPEATED_OFFSETS ) { TrMatchOff = context->enc_last_matchpos_offset[ MatchPos ]; } else { TrMatchOff = MatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } decision_node_ptr[BufPos+EncMatchLength].matchoff = TrMatchOff; } #endif decision_node_ptr[BufPos+EncMatchLength].link = MatchPos; decision_node_ptr[BufPos+EncMatchLength].path = BufPos; /* * Quickly insert data into the search tree without * returning match positions/lengths */ #ifndef INSERT_NEAR_LONG_MATCHES if (MatchPos == 3 && EncMatchLength > 16) { /* * If we found a match 1 character away and it's * length 16 or more, it's probably a string of * zeroes, so don't insert that into the search * engine, since doing so can slow things down * significantly! */ quick_insert_bsearch_findmatch( context, BufPos + 1, BufPos - context->enc_window_size + (1 + 4) /* bp+1 -(ws-4) */ ); } else #endif { for (i = 1; i < (ulong) EncMatchLength; i++) quick_insert_bsearch_findmatch( context, BufPos + i, BufPos + i - context->enc_window_size + 4 ); } BufPos += EncMatchLength; /* * Update the relative match positions */ if (MatchPos >= NUM_REPEATED_OFFSETS) { context->enc_last_matchpos_offset[2] = context->enc_last_matchpos_offset[1]; context->enc_last_matchpos_offset[1] = context->enc_last_matchpos_offset[0]; context->enc_last_matchpos_offset[0] = MatchPos-(NUM_REPEATED_OFFSETS-1); } else if (MatchPos) { ulong t = context->enc_last_matchpos_offset[0]; context->enc_last_matchpos_offset[0] = context->enc_last_matchpos_offset[MatchPos]; context->enc_last_matchpos_offset[MatchPos] = t; } break; } /* * The following code will extend the area spanned by the * set of matches if the current match surpasses the end of * the span. A match of length two that is far is not * accepted, since it would normally be encoded as characters, * thus allowing the paths to converge. */ if (EncMatchLength > 2 || (EncMatchLength == 2 && context->enc_matchpos_table[2] < BREAK_MAX_LENGTH_TWO_OFFSET)) { if (span < (ulong) (BufPos + EncMatchLength)) { long end; long i; end = min(BufPos+EncMatchLength-bpos, LOOK-1); /* * These new positions are undefined for now, since we haven't * gone there yet, so put in the costliest value */ for (i = span-bpos+1; i <= end; i++) context->enc_decision_node[i].numbits = (numbits_t) -1; span = BufPos + EncMatchLength; } } /* * The following code will iterate through all combinations * of match lengths for the current match. It will estimate * the cost of the path from the beginning of LOOK to * BufPos and to every locations spanned by the current * match. If the path through BufPos with the found matches * is estimated to take fewer number of bits to encode than * the previously found match, then the path to the location * is altered. * * The code relies on accurate estimation of the cost of * encoding a character or a match. Furthermore, it requires * a search engine that will store the smallest match offset * of each possible match length. * * A match of length one is simply treated as an unmatched * character. */ /* * Get the estimated number of bits required to encode the * path leading up to BufPos. */ cum_numbits = decision_node_ptr[BufPos].numbits; /* * Calculate the estimated cost of outputting the path through * BufPos and outputting the next character as an unmatched byte */ est = cum_numbits + CHAR_EST(context->enc_MemWindow[BufPos]); /* * Check if it is more efficient to encode the next character * as an unmatched character rather than the previously found * match. If so, then update the cheapest path to BufPos + 1. * * What happens if est == numbits[BufPos-bpos+1]; i.e. it * works out as well to output a character as to output a * match? It's a tough call; however, we will push the * encoder to use matches where possible. */ if (est < decision_node_ptr[BufPos+1].numbits) { decision_node_ptr[BufPos+1].numbits = est; decision_node_ptr[BufPos+1].path = BufPos; } /* * Now, iterate through the remaining match lengths and * compare the new path to the existing. Change the path * if it is found to be more cost effective to go through * BufPos. */ for (i = MIN_MATCH; i <= (ulong) EncMatchLength; i++) { MATCH_EST(i, context->enc_matchpos_table[i], est); est += cum_numbits; /* * If est == numbits[BufPos+i] we want to leave things * alone, since this will tend to force the matches * to be smaller in size, which is beneficial for most * data. */ if (est < decision_node_ptr[BufPos+i].numbits) { decision_node_ptr[BufPos+i].numbits = est; decision_node_ptr[BufPos+i].path = BufPos; decision_node_ptr[BufPos+i].link = context->enc_matchpos_table[i]; #ifdef TRACING { ULONG TrMatchPos = context->enc_matchpos_table[i]; ULONG TrMatchOff; if ( TrMatchPos < NUM_REPEATED_OFFSETS ) { TrMatchOff = context->enc_last_matchpos_offset[ TrMatchPos ]; } else { TrMatchOff = TrMatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } decision_node_ptr[BufPos+i].matchoff = TrMatchOff; } #endif } } } /* continue to loop through span of matches */ /* * Here BufPos == span, ie. a non-matchable character found. The * following code will output the path properly. */ /* * Unfortunately the path is stored in reverse; how to get from * where we are now, to get back to where it all started. * * Traverse the path back to the original starting position * of the LOOK span. Invert the path pointers in order to be * able to traverse back to the current position from the start. */ /* * Count the number of iterations we did, so when we go forwards * we'll do the same amount */ iterations = 0; NextPrevPos = decision_node_ptr[BufPos].path; do { ulong PrevPos; PrevPos = NextPrevPos; NextPrevPos = decision_node_ptr[PrevPos].path; decision_node_ptr[PrevPos].path = BufPos; BufPos = PrevPos; iterations++; } while (BufPos != bpos); if (context->enc_literals + iterations >= (MAX_LITERAL_ITEMS-8) || context->enc_distances + iterations >= (MAX_DIST_ITEMS-8)) { block_end(context, BufPos); } /* * Traverse from the beginning of the LOOK span to the end of * the span along the stored path, outputting matches and * characters appropriately. */ do { if (decision_node_ptr[BufPos].path > BufPos+1) { /* * Path skips over more than 1 character; therefore it's a match */ #ifdef EXTRALONGMATCHES // // If the match length to output here is MAX_MATCH, // this must be the last entry in the decision chain, // and we can extend the match as far as it will go. // long ExMatchPos = decision_node_ptr[ decision_node_ptr[BufPos].path ].link; long ExMatchLength = decision_node_ptr[BufPos].path - BufPos; if ( ExMatchLength == MAX_MATCH ) { ulong ExBufPtr = BufPos + MAX_MATCH; #ifdef TRACING ASSERT( ExMatchOff == (long)decision_node_ptr[ decision_node_ptr[BufPos].path ].matchoff ); #endif /* TRACING */ while (( ExBufPtr < BufPosEndThisChunk ) && ( context->enc_MemWindow[ ExBufPtr ] == context->enc_MemWindow[ ExBufPtr - ExMatchOff ] )) { ++ExBufPtr; ++ExMatchLength; } } OUT_MATCH( ExMatchLength, ExMatchPos ); #ifdef TRACING EncTracingMatch( BufPos, ExMatchLength, ExMatchPos, decision_node_ptr[ decision_node_ptr[BufPos].path ].matchoff ); #endif // TRACING BufPos += ExMatchLength; #else /* ! EXTRALONGMATCHES */ OUT_MATCH( decision_node_ptr[BufPos].path - BufPos, decision_node_ptr[ decision_node_ptr[BufPos].path ].link ); #ifdef TRACING EncTracingMatch( BufPos, decision_node_ptr[BufPos].path - BufPos, decision_node_ptr[ decision_node_ptr[BufPos].path ].link, decision_node_ptr[ decision_node_ptr[BufPos].path ].matchpos ); #endif // TRACING BufPos = decision_node_ptr[BufPos].path; #endif /* ! EXTRALONGMATCHES */ } else { /* * Path goes to the next character; therefore it's a symbol */ OUT_CHAR(context->enc_MemWindow[BufPos]); #ifdef TRACING EncTracingLiteral( BufPos, context->enc_MemWindow[BufPos] ); #endif BufPos++; } } while (--iterations != 0); TREE_CREATE_CHECK(); /* * If we're filling up, and are close to outputting a block, * and it's the first block, then recompress the first N * literals using our accumulated stats. */ if (context->enc_first_block && (context->enc_literals >= (MAX_LITERAL_ITEMS-512) || context->enc_distances >= (MAX_DIST_ITEMS-512))) { if (redo_first_block(context, &BufPos)) goto top_of_main_loop; /* * Unable to redo, so output the block */ block_end(context, BufPos); } } else /* EncMatchLength >= FAST_DECISION_THRESHOLD */ { /* * This code reflects a speed optimization that will always take * a match of length >= FAST_DECISION_THRESHOLD characters. */ /* * The position associated with the match we found */ MatchPos = context->enc_matchpos_table[EncMatchLength]; #ifdef EXTRALONGMATCHES if ( EncMatchLength == MAX_MATCH ) { // // Extend the match length up to end of input buffer // or the current position in the history buffer. // ulong BufPtr = BufPos + MAX_MATCH; long MatchOff; if ( MatchPos < NUM_REPEATED_OFFSETS ) { MatchOff = context->enc_last_matchpos_offset[ MatchPos ]; } else { MatchOff = MatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } while (( BufPtr < BufPosEndThisChunk ) && ( context->enc_MemWindow[ BufPtr ] == context->enc_MemWindow[ BufPtr - MatchOff ] )) { ++BufPtr; ++EncMatchLength; } } #endif /* * Quickly insert match substrings into search tree * (don't look for new matches; just insert the strings) */ #ifndef INSERT_NEAR_LONG_MATCHES if (MatchPos == 3 && EncMatchLength > 16) { quick_insert_bsearch_findmatch( context, BufPos + 1, BufPos - context->enc_window_size + 5 /* bp+1 -(ws-4) */ ); } else #endif { for (i = 1; i < (ulong) EncMatchLength; i++) quick_insert_bsearch_findmatch( context, BufPos + i, BufPos + i - context->enc_window_size + 4 ); } /* * Output the match */ OUT_MATCH(EncMatchLength, MatchPos); #ifdef TRACING { ULONG TrMatchOff; if ( MatchPos < NUM_REPEATED_OFFSETS ) { TrMatchOff = context->enc_last_matchpos_offset[ MatchPos ]; } else { TrMatchOff = MatchPos - ( NUM_REPEATED_OFFSETS - 1 ); } EncTracingMatch( BufPos, EncMatchLength, MatchPos, TrMatchOff ); } #endif // TRACING /* * Advance our position in the window */ BufPos += EncMatchLength; if (MatchPos >= NUM_REPEATED_OFFSETS) { context->enc_last_matchpos_offset[2] = context->enc_last_matchpos_offset[1]; context->enc_last_matchpos_offset[1] = context->enc_last_matchpos_offset[0]; context->enc_last_matchpos_offset[0] = MatchPos-(NUM_REPEATED_OFFSETS-1); } else if (MatchPos) { ulong t = context->enc_last_matchpos_offset[0]; context->enc_last_matchpos_offset[0] = context->enc_last_matchpos_offset[MatchPos]; context->enc_last_matchpos_offset[MatchPos] = t; } /* * Check to see if we're close to overflowing our output arrays, and * output a block if this is the case */ if (context->enc_literals >= (MAX_LITERAL_ITEMS-8) || context->enc_distances >= (MAX_DIST_ITEMS-8)) block_end(context, BufPos); } /* EncMatchLength >= FAST_DECISION_THRESHOLD */ } /* end while ... BufPos < BufPosEnd */ /* * Value of BufPos corresponding to earliest window data */ context->enc_earliest_window_data_remaining = BufPos - context->enc_window_size; /* * We didn't read 32K, so we know for sure that * this was our last block of data. */ if (BytesRead < CHUNK_SIZE) { /* * If we have never output a block, and we haven't * recalculated the stats already, then recalculate * the stats and recompress. */ if (context->enc_first_block) { if (redo_first_block(context, &BufPos)) goto top_of_main_loop; } break; } /* * Remove the last BREAK_LENGTH nodes from the binary search tree, * since we have been inserting strings which contain undefined * data at the end. */ end_pos = BufPos - (context->enc_window_size-4-BREAK_LENGTH); for (i = 1; (i <= BREAK_LENGTH); i++) binary_search_remove_node(context, BufPos-i, end_pos); /* * If we're still in the first window_size + second partition size * bytes in the file then we don't need to copymem() yet. * * RealBufPos is the real position in the file. */ RealBufPos = BufPos - (ulong)(context->enc_RealMemWindow - context->enc_MemWindow); if (RealBufPos < context->enc_window_size + context->enc_encoder_second_partition_size) break; /* * We're about to trash a whole bunch of history with our copymem, * so we'd better redo the first block now if we are ever going to. */ if (context->enc_first_block) { if (redo_first_block(context, &BufPos)) goto top_of_main_loop; } /* * We're about to remove a large number of symbols from the window. * Test to see whether, if we were to output a block now, our compressed * output size would be larger than our uncompressed data. If so, then * we will output an uncompressed block. * * The reason we have to do this check here, is that data in the * window is about to be destroyed. We can't simply put this check in * the block outputting code, since there is no guarantee that the * memory window contents corresponding to everything in that block, * are still around - all we'd have would be a set of literals and * distances, when we need all the uncompressed literals to output * an uncompressed block. */ /* * What value of bufpos corresponds to the oldest data we have in the * buffer? * * After the memory copy, that will be the current buffer position, * minus window_size. */ /* * The end of the data buffer is reached, more data needs to be read * and the existing data must be shifted into the history window. * * MSVC 4.x generates code which does REP MOVSD so no need to * write this in assembly. */ copymem( &context->enc_RealMemWindow[context->enc_encoder_second_partition_size], &context->enc_RealMemWindow[0], context->enc_window_size ); copymem( &context->enc_RealLeft[context->enc_encoder_second_partition_size], &context->enc_RealLeft[0], sizeof(ulong)*context->enc_window_size ); copymem( &context->enc_RealRight[context->enc_encoder_second_partition_size], &context->enc_RealRight[0], sizeof(ulong)*context->enc_window_size ); context->enc_earliest_window_data_remaining = BufPos - context->enc_window_size; /* * The following bit of code is CRUCIAL yet unorthodox in function * and serves as a speed and syntax optimization and makes the code * easier to understand once grasped. * * The three main buffers, context->enc_MemWindow, context->enc_Left and context->enc_Right, * are referensed by BufPos and SearchPos relative to the current * compression window locations. When the encoder reaches the end * of its block of input memory, the data in the input buffer is * shifted into the compression history window and the new input * stream is loaded. Typically the BufPos pointer would be reduced * to signify the replaced data. However, this code reduces the * base pointers to reflect the shift of data, and leaves the BufPos * pointer in its current state. Therefore, the BufPos pointer is * an absolute pointer reflecting the position in the input stream, * and NOT the position in the buffer. The base pointers will point * to invalid memory locations with addresses smaller than the * actual array base pointers. However, when the two pointers are * added together, &(context->enc_MemWindow+BufPos), it will point to the * correct and valid position in the buffer. */ context->enc_MemWindow -= context->enc_encoder_second_partition_size; context->enc_Left -= context->enc_encoder_second_partition_size; context->enc_Right -= context->enc_encoder_second_partition_size; break; } /* * Store BufPos in global variable */ context->enc_BufPos = BufPos; } static void block_end(t_encoder_context *context, long BufPos) { context->enc_first_block = false; context->enc_need_to_recalc_stats = true; output_block(context); if (context->enc_literals < TREE_CREATE_INTERVAL) { context->enc_next_tree_create = TREE_CREATE_INTERVAL; } else { context->enc_next_tree_create = context->enc_literals + TREE_CREATE_INTERVAL; /* recreate right away */ } context->enc_bufpos_last_output_block = BufPos; } static bool redo_first_block(t_encoder_context *context, long *bufpos_ptr) { long start_at; long earliest_can_start_at; long pos_in_file; long history_needed; long history_avail; long BufPos; long split_at_literal; context->enc_first_block = false; BufPos = *bufpos_ptr; /* * For the first context->enc_window size bytes in the file, we don't * need to have context->enc_window size bytes around. * * For anything after that, though, we do need to have window_size * previous bytes to look into. */ /* * How many bytes are we into the file? */ pos_in_file = BufPos - context->enc_window_size; /* * First let's figure out the total history required from * BufPos backwards. For starters, we need all the bytes * we're going to recompress. We get that by seeing the * last time we output a block. */ history_needed = BufPos - context->enc_bufpos_last_output_block; /* * Plus we will need window_size bytes before that (for matching * into) unless we're looking within the first window_size * bytes of the file. */ if (context->enc_bufpos_last_output_block-context->enc_window_size < context->enc_window_size) history_needed += context->enc_bufpos_last_output_block - context->enc_window_size; else history_needed += context->enc_window_size; history_avail = (ulong)(&context->enc_MemWindow[BufPos] - &context->enc_RealMemWindow[0]); if (history_needed <= history_avail) { earliest_can_start_at = context->enc_bufpos_last_output_block; } else { /* * Not enough history available */ return false; } start_at = earliest_can_start_at; split_block( context, 0, context->enc_literals, context->enc_distances, &split_at_literal, NULL /* don't need # distances returned */ ); get_block_stats( context, 0, 0, split_at_literal ); create_trees(context, false); /* don't generate codes */ fix_tree_cost_estimates(context); #ifdef MULTIPLE_SEARCH_TREES /* * Now set all the tree root pointers to NULL * (don't need to reset the left/right pointers). */ memset(context->enc_tree_root, 0, NUM_SEARCH_TREES * sizeof(ulong)); #else context->enc_single_tree_root = 0; #endif /* * Clear item array and reset literal and distance * counters */ memset(context->enc_ItemType, 0, (MAX_LITERAL_ITEMS/8)); /* * Reset encoder state */ context->enc_last_matchpos_offset[0] = 1; context->enc_last_matchpos_offset[1] = 1; context->enc_last_matchpos_offset[2] = 1; context->enc_repeated_offset_at_literal_zero[0] = 1; context->enc_repeated_offset_at_literal_zero[1] = 1; context->enc_repeated_offset_at_literal_zero[2] = 1; context->enc_input_running_total = 0; context->enc_literals = 0; context->enc_distances = 0; context->enc_need_to_recalc_stats = true; context->enc_next_tree_create = split_at_literal; *bufpos_ptr = start_at; return true; }