Make Attaching an Empty Dict Behave the Same as Using it Directly
authorW. Felix Handte <w@felixhandte.com>
Tue, 6 Aug 2019 19:24:51 +0000 (15:24 -0400)
committerW. Felix Handte <w@felixhandte.com>
Tue, 6 Aug 2019 22:50:33 +0000 (18:50 -0400)
When using an empty dictionary, we bail out of loading or attaching it in
ways that leave the working context in potentially slightly different states.
In particular, in some paths, we will cause the currentOffset to be non-zero,
while in others we would allow it to remain 0.

This difference in behavior is perfectly harmless, but in some situations, it
can produce slight differences in the compressed output. For sanity's sake,
we currently try to maintain a strict correspondence between the behavior of
the dict attachment and the dict loading paths. This patch restores them to
behaving identically.

This shouldn't have any negative side-effects, as far as I can tell. When
writing the dict attachment code, I tried to preserve zeroed currentOffsets
when possible, since they benchmarked as very slightly faster. However, the
case of attaching an empty dictionary is probably rare enought that it's
acceptable to minisculely degrade performance in that corner case.

lib/lz4.c

index 0849505..147a8d6 100644 (file)
--- a/lib/lz4.c
+++ b/lib/lz4.c
@@ -1408,18 +1408,18 @@ int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
      * there are only valid offsets in the window, which allows an optimization
      * in LZ4_compress_fast_continue() where it uses noDictIssue even when the
      * dictionary isn't a full 64k. */
-
-    if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB;
-    base = dictEnd - 64 KB - dict->currentOffset;
-    dict->dictionary = p;
-    dict->dictSize = (U32)(dictEnd - p);
     dict->currentOffset += 64 KB;
-    dict->tableType = tableType;
 
     if (dictSize < (int)HASH_UNIT) {
         return 0;
     }
 
+    if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB;
+    base = dictEnd - dict->currentOffset;
+    dict->dictionary = p;
+    dict->dictSize = (U32)(dictEnd - p);
+    dict->tableType = tableType;
+
     while (p <= dictEnd-HASH_UNIT) {
         LZ4_putPosition(p, dict->hashTable, tableType, base);
         p+=3;
@@ -1435,15 +1435,16 @@ void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dict
      */
     LZ4_resetStream_fast(workingStream);
 
+    /* If the current offset is zero, we will never look in the
+     * external dictionary context, since there is no value a table
+     * entry can take that indicate a miss. In that case, we need
+     * to bump the offset to something non-zero.
+     */
+    if (workingStream->internal_donotuse.currentOffset == 0) {
+        workingStream->internal_donotuse.currentOffset = 64 KB;
+    }
+
     if (dictionaryStream != NULL && dictionaryStream->internal_donotuse.dictSize > 0) {
-        /* If the current offset is zero, we will never look in the
-         * external dictionary context, since there is no value a table
-         * entry can take that indicate a miss. In that case, we need
-         * to bump the offset to something non-zero.
-         */
-        if (workingStream->internal_donotuse.currentOffset == 0) {
-            workingStream->internal_donotuse.currentOffset = 64 KB;
-        }
         workingStream->internal_donotuse.dictCtx = &(dictionaryStream->internal_donotuse);
     } else {
         workingStream->internal_donotuse.dictCtx = NULL;