-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathzlib_accel.cpp
More file actions
2680 lines (2410 loc) · 102 KB
/
Copy pathzlib_accel.cpp
File metadata and controls
2680 lines (2410 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (C) 2025 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
#include "zlib_accel.h"
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/param.h>
#include <unistd.h>
#include <cstdint>
#include <cstring>
#include <memory>
#include <new>
#include <string>
#include <utility>
#include "config/config.h"
#include "logging.h"
#include "sharded_map.h"
#ifdef USE_IAA
#include "iaa.h"
#endif
#ifdef USE_IGZIP
#include "igzip.h"
#endif
#ifdef USE_QAT
#include "qat.h"
#endif
#include "statistics.h"
using namespace config;
// Window bits: 15 = zlib (deflate) format, 31 = gzip format
static constexpr int kWindowBitsZlib = 15;
static constexpr int kWindowBitsGzip = 31;
// Disable cfi-icall as it makes calls to orig* functions fail
#if defined(__clang__)
#pragma clang attribute push(__attribute__((no_sanitize("cfi-icall"))), \
apply_to = function)
#endif
// Original zlib functions
static int (*orig_deflateInit_)(z_streamp strm, int level, const char* version,
int stream_size);
static int (*orig_deflateInit2_)(z_streamp strm, int level, int method,
int window_bits, int mem_level, int strategy,
const char* version, int stream_size);
static int (*orig_deflateSetDictionary)(z_streamp strm, const Bytef* dictionary,
uInt dictLength);
static int (*orig_deflate)(z_streamp strm, int flush);
static int (*orig_deflateEnd)(z_streamp strm);
static int (*orig_deflateReset)(z_streamp strm);
static int (*orig_deflateResetKeep)(z_streamp strm);
static int (*orig_deflateParams)(z_streamp strm, int level, int strategy);
static int (*orig_deflateCopy)(z_streamp dest, z_streamp source);
static int (*orig_inflateInit_)(z_streamp strm, const char* version,
int stream_size);
static int (*orig_inflateInit2_)(z_streamp strm, int window_bits,
const char* version, int stream_size);
static int (*orig_inflateSetDictionary)(z_streamp strm, const Bytef* dictionary,
uInt dictLength);
static int (*orig_inflate)(z_streamp strm, int flush);
static int (*orig_inflateEnd)(z_streamp strm);
static int (*orig_inflateReset)(z_streamp strm);
static int (*orig_inflateResetKeep)(z_streamp strm);
static int (*orig_inflateReset2)(z_streamp strm, int windowBits);
static int (*orig_inflateCopy)(z_streamp dest, z_streamp source);
static int (*orig_compress)(Bytef* dest, uLongf* destLen, const Bytef* source,
uLong sourceLen);
static int (*orig_compress2)(Bytef* dest, uLongf* destLen, const Bytef* source,
uLong sourceLen, int level);
static int (*orig_uncompress)(Bytef* dest, uLongf* destLen, const Bytef* source,
uLong sourceLen);
static int (*orig_uncompress2)(Bytef* dest, uLongf* destLen,
const Bytef* source, uLong* sourceLen);
static gzFile (*orig_gzopen)(const char* path, const char* mode);
static gzFile (*orig_gzdopen)(int fd, const char* mode);
static int (*orig_gzwrite)(gzFile file, voidpc buf, unsigned len);
static int (*orig_gzread)(gzFile file, voidp buf, unsigned len);
static int (*orig_gzclose)(gzFile file);
static int (*orig_gzeof)(gzFile file);
// Forward declaration — defined after DeflateStreamSettings,
// InflateStreamSettings, and GzipFiles class definitions below
static void InitStreamRegistries();
// Initialize/cleanup functions when library is loaded
static int init_zlib_accel(void) __attribute__((constructor));
static void cleanup_zlib_accel(void) __attribute__((destructor));
// Number of orig_* symbols that failed to resolve. Only used for the summary
// log below: each wrapper checks the specific pointer it needs, so one missing
// symbol degrades only the paths that actually call it.
static int missing_symbol_count = 0;
// Macro that loads symbols. A failure is logged but does not stop the sequence:
// returning early from the constructor would leave every later orig_* pointer
// null (and the return value of a constructor is ignored anyway), so keep going
// and resolve as many as possible.
#define LOAD_SYMBOL(fptr, type, name) \
do { \
dlerror(); \
fptr = reinterpret_cast<type>(dlsym(RTLD_NEXT, name)); \
const char* error = dlerror(); \
if (error != nullptr) { \
Log(LogLevel::LOG_ERROR, "init_zlib_accel Line ", __LINE__, \
"Failed to load symbol '", name, "': ", error, "\n"); \
fptr = nullptr; \
missing_symbol_count++; \
} else if (fptr == nullptr) { \
Log(LogLevel::LOG_ERROR, "init_zlib_accel Line ", __LINE__, " Symbol '", \
name, "' resolved to NULL\n"); \
missing_symbol_count++; \
} \
} while (0)
static int init_zlib_accel(void) {
// Load deflate functions
LOAD_SYMBOL(orig_deflateInit_, int (*)(z_streamp, int, const char*, int),
"deflateInit_");
LOAD_SYMBOL(orig_deflateInit2_,
int (*)(z_streamp, int, int, int, int, int, const char*, int),
"deflateInit2_");
LOAD_SYMBOL(orig_deflateSetDictionary, int (*)(z_streamp, const Bytef*, uInt),
"deflateSetDictionary");
LOAD_SYMBOL(orig_deflate, int (*)(z_streamp, int), "deflate");
LOAD_SYMBOL(orig_deflateEnd, int (*)(z_streamp), "deflateEnd");
LOAD_SYMBOL(orig_deflateReset, int (*)(z_streamp), "deflateReset");
LOAD_SYMBOL(orig_deflateResetKeep, int (*)(z_streamp), "deflateResetKeep");
LOAD_SYMBOL(orig_deflateParams, int (*)(z_streamp, int, int),
"deflateParams");
LOAD_SYMBOL(orig_deflateCopy, int (*)(z_streamp, z_streamp), "deflateCopy");
// Load inflate functions
LOAD_SYMBOL(orig_inflateInit_, int (*)(z_streamp, const char*, int),
"inflateInit_");
LOAD_SYMBOL(orig_inflateInit2_, int (*)(z_streamp, int, const char*, int),
"inflateInit2_");
LOAD_SYMBOL(orig_inflateSetDictionary, int (*)(z_streamp, const Bytef*, uInt),
"inflateSetDictionary");
LOAD_SYMBOL(orig_inflate, int (*)(z_streamp, int), "inflate");
LOAD_SYMBOL(orig_inflateEnd, int (*)(z_streamp), "inflateEnd");
LOAD_SYMBOL(orig_inflateReset, int (*)(z_streamp), "inflateReset");
LOAD_SYMBOL(orig_inflateResetKeep, int (*)(z_streamp), "inflateResetKeep");
LOAD_SYMBOL(orig_inflateReset2, int (*)(z_streamp, int), "inflateReset2");
LOAD_SYMBOL(orig_inflateCopy, int (*)(z_streamp, z_streamp), "inflateCopy");
// Load compress/uncompress functions
LOAD_SYMBOL(orig_compress, int (*)(Bytef*, uLongf*, const Bytef*, uLong),
"compress");
LOAD_SYMBOL(orig_compress2,
int (*)(Bytef*, uLongf*, const Bytef*, uLong, int), "compress2");
LOAD_SYMBOL(orig_uncompress, int (*)(Bytef*, uLongf*, const Bytef*, uLong),
"uncompress");
LOAD_SYMBOL(orig_uncompress2, int (*)(Bytef*, uLongf*, const Bytef*, uLong*),
"uncompress2");
// Load gzip functions
LOAD_SYMBOL(orig_gzopen, gzFile(*)(const char*, const char*), "gzopen");
LOAD_SYMBOL(orig_gzdopen, gzFile(*)(int, const char*), "gzdopen");
LOAD_SYMBOL(orig_gzwrite, int (*)(gzFile, voidpc, unsigned), "gzwrite");
LOAD_SYMBOL(orig_gzread, int (*)(gzFile, voidp, unsigned), "gzread");
LOAD_SYMBOL(orig_gzclose, int (*)(gzFile), "gzclose");
LOAD_SYMBOL(orig_gzeof, int (*)(gzFile), "gzeof");
if (missing_symbol_count > 0) {
Log(LogLevel::LOG_ERROR, "init_zlib_accel Line ", __LINE__, " ",
missing_symbol_count,
" zlib symbol(s) could not be resolved; the affected entry points fall "
"back to zlib where possible and return Z_VERSION_ERROR where not\n");
}
// Load configuration file; on failure (file absent or is a symlink) continue
// with compiled-in defaults — a missing config is not fatal.
std::string config_file_content;
const bool config_loaded = config::LoadConfigFile(config_file_content);
if (!config_loaded) {
Log(LogLevel::LOG_ERROR,
"Failed to load configuration file, continuing with defaults\n");
}
InitStreamRegistries();
#if defined(DEBUG_LOG) || defined(ENABLE_STATISTICS)
if (config_loaded && !config::log_file.empty()) {
CreateLogFile(config::log_file.c_str());
}
#endif
return 0;
}
static void cleanup_zlib_accel(void) {
#if defined(DEBUG_LOG) || defined(ENABLE_STATISTICS)
CloseLogFile();
#endif
}
#undef LOAD_SYMBOL
// Avoid recursive call (e.g., if QATzip falls back to zlib internally)
static thread_local bool in_call = false;
constexpr uint8_t ZLIB_FDICT_MASK = 0x20;
#ifdef DEBUG_LOG
// Readable name for the numeric path in log output. Only referenced from Log(),
// which compiles away entirely when DEBUG_LOG is off.
static const char* ExecutionPathName(ExecutionPath path) {
switch (path) {
case UNDEFINED:
return "UNDEFINED";
case ZLIB:
return "ZLIB";
case QAT:
return "QAT";
case IAA:
return "IAA";
case IGZIP:
return "IGZIP";
}
return "UNKNOWN";
}
#endif // DEBUG_LOG
struct DeflateSettings {
DeflateSettings(int _level, int _method, int _window_bits, int _mem_level,
int _strategy)
: level(_level),
method(_method),
window_bits(_window_bits),
mem_level(_mem_level),
strategy(_strategy) {}
int level;
int method;
int window_bits;
int mem_level;
int strategy;
ExecutionPath path = UNDEFINED;
struct isal_zstream* isal_strm = nullptr;
// Set once the shim has reported Z_STREAM_END for this stream. Only an
// offloaded completion sets it: an accelerator finishes the stream without
// ever feeding zlib's own state, so zlib cannot report the terminal state
// afterwards and every later call would be dispatched from scratch. A
// ZLIB-path stream is left alone, since zlib tracks this itself.
bool stream_end_reached = false;
};
struct InflateSettings {
InflateSettings(int _window_bits) : window_bits(_window_bits) {}
int window_bits;
ExecutionPath path = UNDEFINED;
struct inflate_state* isal_strm = nullptr;
// See DeflateSettings::stream_end_reached.
bool stream_end_reached = false;
};
// isal_strm is a raw pointer, so destroying a settings object does not free the
// ISA-L stream it owns. Every path that discards an entry has to come through
// here: *End() when the caller is done with the stream, and Set()/SetFromCopy()
// when a new entry replaces one that is still holding state.
static void ReleaseDeflateIgzipState(
const std::shared_ptr<DeflateSettings>& settings) {
if (settings == nullptr || settings->isal_strm == nullptr) {
return;
}
#ifdef USE_IGZIP
EndCompressIGZIP(settings->isal_strm);
#endif
settings->isal_strm = nullptr;
}
static void ReleaseInflateIgzipState(
const std::shared_ptr<InflateSettings>& settings) {
if (settings == nullptr || settings->isal_strm == nullptr) {
return;
}
#ifdef USE_IGZIP
EndUncompressIGZIP(settings->isal_strm);
#endif
settings->isal_strm = nullptr;
}
class DeflateStreamSettings {
public:
void Set(z_streamp strm, int level, int method, int window_bits,
int mem_level, int strategy) {
auto previous = map.Get(strm);
auto settings = std::make_shared<DeflateSettings>(
level, method, window_bits, mem_level, strategy);
map.Set(strm, std::move(settings));
// A second deflateInit*() on a stream that was never ended replaces an
// entry that may still own an ISA-L stream, which nothing can reach once
// the entry is gone. See SetFromCopy() for the ordering.
ReleaseDeflateIgzipState(previous);
}
// Registers dest as a copy of an already-tracked stream, for deflateCopy().
// Deliberately not a copy of the DeflateSettings object: that would carry
// isal_strm over and leave the two streams sharing one ISA-L state.
//
// Reports failure instead of throwing: the caller is an exported zlib symbol,
// so an exception escaping here would cross into a C caller that cannot catch
// it. The catch is deliberately unqualified -- besides bad_alloc from the
// allocations here, ShardedMap::Set() locks a std::shared_mutex on the
// non-TBB build and so can throw std::system_error. Returning false lets the
// caller undo the copy and report Z_MEM_ERROR, which alongside
// Z_STREAM_ERROR is the only failure zlib documents for deflateCopy().
bool SetFromCopy(z_streamp dest, const DeflateSettings& source) {
// dest may already be an initialized, used stream whose entry owns an ISA-L
// stream. Read that entry before replacing it, and release it only after
// the replacement has landed: freeing first would leave a dangling
// isal_strm in the map if the work below throws. The shared_ptr keeps the
// old settings alive past map.Set().
auto previous = map.Get(dest);
try {
auto settings = std::make_shared<DeflateSettings>(
source.level, source.method, source.window_bits, source.mem_level,
source.strategy);
settings->path = source.path;
settings->stream_end_reached = source.stream_end_reached;
map.Set(dest, std::move(settings));
} catch (...) {
Log(LogLevel::LOG_ERROR,
"SetFromCopy() failed to register deflate stream ",
static_cast<void*>(dest), "\n");
return false;
}
ReleaseDeflateIgzipState(previous);
return true;
}
void Unset(z_streamp strm) { map.Unset(strm); }
std::shared_ptr<DeflateSettings> Get(z_streamp strm) { return map.Get(strm); }
void Init() { map.Init(); }
private:
ShardedMap<z_streamp, std::shared_ptr<DeflateSettings>> map;
};
DeflateStreamSettings deflate_stream_settings;
class InflateStreamSettings {
public:
void Set(z_streamp strm, int window_bits) {
auto previous = map.Get(strm);
auto settings = std::make_shared<InflateSettings>(window_bits);
map.Set(strm, std::move(settings));
// See the deflate-side Set().
ReleaseInflateIgzipState(previous);
}
// Registers dest as a copy of an already-tracked stream, for inflateCopy().
// isal_clone is passed in rather than copied from source so that ownership of
// the cloned ISA-L state is explicit: the caller allocates it, this entry
// point hands it to the new settings, and inflateEnd() frees it. Ownership
// therefore transfers only when this returns true; on false the clone is
// still the caller's to free, along with zlib's half of the copy. See the
// deflate-side comment for why failure is reported, not thrown, and for the
// ordering of the release below.
bool SetFromCopy(z_streamp dest, const InflateSettings& source,
struct inflate_state* isal_clone) {
auto previous = map.Get(dest);
try {
auto settings = std::make_shared<InflateSettings>(source.window_bits);
settings->path = source.path;
settings->isal_strm = isal_clone;
settings->stream_end_reached = source.stream_end_reached;
map.Set(dest, std::move(settings));
} catch (...) {
Log(LogLevel::LOG_ERROR,
"SetFromCopy() failed to register inflate stream ",
static_cast<void*>(dest), "\n");
return false;
}
ReleaseInflateIgzipState(previous);
return true;
}
void Unset(z_streamp strm) { map.Unset(strm); }
std::shared_ptr<InflateSettings> Get(z_streamp strm) { return map.Get(strm); }
void Init() { map.Init(); }
private:
ShardedMap<z_streamp, std::shared_ptr<InflateSettings>> map;
};
InflateStreamSettings inflate_stream_settings;
static void SetDeflatePath(const std::shared_ptr<DeflateSettings>& settings,
ExecutionPath new_path) {
if (settings == nullptr || settings->path == new_path) {
return;
}
settings->path = new_path;
}
static void SetInflatePath(const std::shared_ptr<InflateSettings>& settings,
ExecutionPath new_path) {
if (settings == nullptr || settings->path == new_path) {
return;
}
settings->path = new_path;
}
// Shim-side state work every deflate reset entry point performs. Only the path
// is cleared. zlib's deflateReset keeps the compression level and strategy,
// including any set later by deflateParams(), so the recorded level must
// survive a reset too or path selection would disagree with the level zlib is
// actually using. The terminal-state flag has to go, though: a reset stream is
// ready to compress again, and leaving it set would wedge every later deflate()
// at Z_STREAM_END.
static void ResetDeflateStreamState(
const std::shared_ptr<DeflateSettings>& settings) {
if (settings == nullptr) {
return;
}
SetDeflatePath(settings, UNDEFINED);
settings->stream_end_reached = false;
#ifdef USE_IGZIP
if (settings->isal_strm != nullptr) {
// Keeping the recorded level is not sufficient for the ISA-L stream:
// isal_deflate_reset() deliberately preserves level and level_buf, and
// deflate() only calls InitCompressIGZIP() when isal_strm is null, so a
// level that deflateParams() changed since this stream was built would
// leave the next stream running at the old ISA-L level. Discard the stream
// in that case and let deflate() rebuild it from the current setting; the
// common reset, where the level did not change, keeps the stream and its
// level_buf allocation. The reverse ordering -- reset first, then
// deflateParams() -- is handled in deflateParams().
if (CompressLevelChangedIGZIP(settings->isal_strm, settings->level)) {
EndCompressIGZIP(settings->isal_strm);
settings->isal_strm = nullptr;
} else {
ResetCompressIGZIP(settings->isal_strm);
}
}
#endif
}
// Same for the inflate side. A reset stream is ready to decode again; leaving
// the terminal state set would wedge every later inflate() at Z_STREAM_END.
static void ResetInflateStreamState(
const std::shared_ptr<InflateSettings>& settings) {
if (settings == nullptr) {
return;
}
SetInflatePath(settings, UNDEFINED);
settings->stream_end_reached = false;
if (settings->isal_strm != nullptr) {
#ifdef USE_IGZIP
ResetUncompressIGZIP(settings->isal_strm);
#endif
}
}
// zlib's Z_NO_COMPRESSION (0) asks for stored, uncompressed deflate blocks. No
// backend can produce those: ISA-L's level 0 is still LZ77+Huffman ("fastest"),
// and QAT and IAA take no level argument at all -- all three would silently
// compress data the caller asked to be stored. Levels outside zlib's -1..9
// range are rejected by zlib itself, so leave those for zlib to report too.
// Everything else maps onto an ISA-L level in InitCompressIGZIP().
static bool IsOffloadableCompressionLevel(int level) {
return level == Z_DEFAULT_COMPRESSION || (level >= 1 && level <= 9);
}
// True when ISA-L holds live state for this stream: it has emitted a header and
// may still hold unflushed data, so the stream can neither be handed to zlib
// nor rebuilt at a different compression level without corrupting the output.
// Both deflate()'s level-0 pin and deflateParams()' discard of a stream built
// for a superseded level exempt such a stream, and deflate() uses it to keep an
// already-started IGZIP stream on IGZIP.
static bool IgzipOwnsDeflateStream(
const std::shared_ptr<DeflateSettings>& settings) {
return settings != nullptr && settings->path == IGZIP &&
settings->isal_strm != nullptr;
}
// Z_BLOCK and Z_TREES ask inflate() to stop early -- at the next deflate block
// boundary, and additionally at the end of each block header -- and to report
// the bit position reached in z_stream.data_type. No backend can do either.
// QAT and IAA decompress whole streams in one submission with no notion of a
// block boundary, and ISA-L transits ISAL_BLOCK_NEW_HDR/ISAL_BLOCK_HDR inside
// a single isal_inflate() call with no way to stop there. Left offloaded, such
// a call over-delivers -- it returns the whole stream instead of one block and
// leaves data_type untouched -- so the output is a correct prefix but the bit
// accounting the caller asked for is silently missing. Applications pass these
// values to append to, splice, or randomly access deflate streams, i.e. the
// accounting *is* the request. zlib is the only implementation here that can
// honor it, so route the stream there.
static bool IsOffloadableInflateFlush(int flush) {
return flush != Z_BLOCK && flush != Z_TREES;
}
int ZEXPORT deflateInit_(z_streamp strm, int level, const char* version,
int stream_size) {
Log(LogLevel::LOG_INFO, "deflateInit_ Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", level ", level, "\n");
// The shim has no deflate implementation of its own, so a missing symbol is
// unrecoverable for this entry point. Report it the way zlib reports an
// unusable library and register nothing, which keeps deflate() off this
// stream.
if (orig_deflateInit_ == nullptr) {
return Z_VERSION_ERROR;
}
// Register only once zlib has accepted the stream. On failure the app never
// calls deflateEnd, so an entry made here would outlive the z_streamp; and
// zlib leaves an already-initialized stream untouched when it rejects new
// parameters, so the previous settings must stay in place.
int ret = orig_deflateInit_(strm, level, version, stream_size);
if (ret == Z_OK) {
deflate_stream_settings.Set(strm, level, Z_DEFLATED, kWindowBitsZlib, 8,
Z_DEFAULT_STRATEGY);
}
return ret;
}
int ZEXPORT deflateInit2_(z_streamp strm, int level, int method,
int window_bits, int mem_level, int strategy,
const char* version, int stream_size) {
Log(LogLevel::LOG_INFO, "deflateInit2_ Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", level ", level, ", window_bits ",
window_bits, " \n");
if (orig_deflateInit2_ == nullptr) {
return Z_VERSION_ERROR;
}
int ret = orig_deflateInit2_(strm, level, method, window_bits, mem_level,
strategy, version, stream_size);
if (ret == Z_OK) {
deflate_stream_settings.Set(strm, level, method, window_bits, mem_level,
strategy);
}
return ret;
}
int ZEXPORT deflateSetDictionary(z_streamp strm, const Bytef* dictionary,
uInt dictLength) {
if (!configs[IGNORE_ZLIB_DICTIONARY]) {
Log(LogLevel::LOG_INFO, "deflateSetDictionary Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", dictLength ", dictLength, "\n");
auto deflate_settings = deflate_stream_settings.Get(strm);
// Reject mid-stream: if an accelerator is active, the underlying zlib
// stream has not been advanced, so orig_deflateSetDictionary would
// incorrectly accept the call. Per zlib spec, dictionary must be set before
// compression begins.
if (deflate_settings != nullptr && deflate_settings->path != UNDEFINED &&
deflate_settings->path != ZLIB) {
return Z_STREAM_ERROR;
}
if (orig_deflateSetDictionary == nullptr) {
return Z_VERSION_ERROR;
}
const int ret = orig_deflateSetDictionary(strm, dictionary, dictLength);
if (ret == Z_OK) {
SetDeflatePath(deflate_settings, ZLIB);
}
return ret;
}
Log(LogLevel::LOG_INFO, "deflateSetDictionary Line ", __LINE__,
" ignored because ignore_zlib_dictionary is set to ",
configs[IGNORE_ZLIB_DICTIONARY], "\n");
return Z_OK;
}
int ZEXPORT deflateParams(z_streamp strm, int level, int strategy) {
Log(LogLevel::LOG_INFO, "deflateParams Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", level ", level, ", strategy ", strategy,
"\n");
if (orig_deflateParams == nullptr) {
return Z_VERSION_ERROR;
}
const int ret = orig_deflateParams(strm, level, strategy);
// On Z_BUF_ERROR zlib documents the parameters as unchanged, so only record
// them when zlib actually accepted the change -- the same ret == Z_OK gating
// that deflateInit*() and both *SetDictionary() functions use.
if (ret == Z_OK) {
auto deflate_settings = deflate_stream_settings.Get(strm);
if (deflate_settings != nullptr) {
deflate_settings->level = level;
deflate_settings->strategy = strategy;
#ifdef USE_IGZIP
// deflateReset() gives up an ISA-L stream built for a level that has
// since changed, but the two orderings need separate handling: reset
// first and the level still matches at that point, so the stream is kept,
// and then this call changes the level under a stream deflate() will
// reuse as-is (it only builds one when isal_strm is null). Discard it
// here so the next deflate() rebuilds it at the level just requested.
//
// A stream ISA-L already owns is the one case to leave alone: it holds a
// header plus unflushed data, so it cannot be rebuilt mid-stream. That
// leaves the new level unhonored until the next reset, the same
// deliberate mid-stream residual as the level-0 pin's exemption in
// deflate().
if (!IgzipOwnsDeflateStream(deflate_settings) &&
deflate_settings->isal_strm != nullptr &&
CompressLevelChangedIGZIP(deflate_settings->isal_strm, level)) {
EndCompressIGZIP(deflate_settings->isal_strm);
deflate_settings->isal_strm = nullptr;
}
#endif
}
}
return ret;
}
int ZEXPORT deflate(z_streamp strm, int flush) {
auto deflate_settings = deflate_stream_settings.Get(strm);
INCREMENT_STAT(DEFLATE_COUNT);
PrintStats();
// Without per-stream settings there is nothing to base a path decision on, so
// hand the call straight to zlib.
if (deflate_settings == nullptr) {
return orig_deflate != nullptr ? orig_deflate(strm, flush)
: Z_VERSION_ERROR;
}
Log(LogLevel::LOG_INFO, "deflate Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", avail_in ", strm->avail_in, ", avail_out ",
strm->avail_out, ", flush ", flush, ", in_call ", in_call, ", path ",
static_cast<int>(deflate_settings->path), ", path_name ",
ExecutionPathName(deflate_settings->path), ", window_bits ",
deflate_settings->window_bits, ", total_in ", strm->total_in,
", total_out ", strm->total_out, ", adler ", strm->adler, "\n");
// A stream an accelerator already finished has to be refused here, above both
// path selection and the zlib fall-through: the offload never fed zlib's own
// deflate state, so orig_deflate() would see a stream still at INIT_STATE and
// emit a second header (or a whole empty stream) after a finished one. Reply
// with what zlib replies once its own state is at FINISH_STATE.
if (deflate_settings->stream_end_reached) {
// Same tests in the same order as zlib's deflate(), which validates its
// parameters before reporting the terminal state. strm->msg follows zlib
// too: it is only written where zlib rejects through ERR_RETURN, and the
// strings are the ones that macro would pick (z_errmsg[] in zutil.c).
int ret = Z_STREAM_END;
if (flush > Z_BLOCK || flush < 0) {
// The flush range is zlib's first check and a plain return, not an
// ERR_RETURN, so an out-of-range value leaves msg as the caller left it.
ret = Z_STREAM_ERROR;
} else if (strm->next_out == nullptr ||
(strm->avail_in != 0 && strm->next_in == nullptr) ||
flush != Z_FINISH) {
// Once the stream is finished no flush but Z_FINISH is accepted. zlib
// rejects all three of these in one ERR_RETURN.
strm->msg = const_cast<char*>("stream error");
ret = Z_STREAM_ERROR;
} else if (strm->avail_out == 0) {
strm->msg = const_cast<char*>("buffer error");
ret = Z_BUF_ERROR;
} else if (strm->avail_in != 0) {
// "user must not provide more input after the first FINISH".
strm->msg = const_cast<char*>("buffer error");
ret = Z_BUF_ERROR;
}
Log(LogLevel::LOG_INFO, "deflate Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", stream already ended, return code ", ret,
"\n");
INCREMENT_STAT(DEFLATE_STREAM_END_COUNT);
INCREMENT_STAT_COND(ret < 0, DEFLATE_ERROR_COUNT);
return ret;
}
// Everything below reads next_in and next_out -- the offload hands both to a
// vendor library -- while zlib rejects a null pointer with data behind it
// before it looks at anything else. Delegate such a call so zlib produces
// that rejection instead of the shim dereferencing what zlib is about to
// refuse. zlib's parameter checks touch no stream state, so a delegated call
// is indistinguishable from an unshimmed one.
// Counted like the fall-through below rather than like an early exit: the
// call did reach zlib, and its rejection is an error the statistics should
// show.
if (strm->next_out == nullptr ||
(strm->avail_in != 0 && strm->next_in == nullptr)) {
if (orig_deflate == nullptr) {
return Z_VERSION_ERROR;
}
const int ret = orig_deflate(strm, flush);
INCREMENT_STAT(DEFLATE_ZLIB_COUNT);
INCREMENT_STAT_COND(ret < 0, DEFLATE_ERROR_COUNT);
return ret;
}
// The compression level is a property of the whole stream, not of one call,
// so decide it here rather than discovering it when InitCompressIGZIP()
// rejects the level. Pinning the path (rather than only clearing
// igzip_available) is what lets the stream reach orig_deflate even when
// use_zlib_compress=0: the request was never an offload candidate, so this is
// not a fallback -- the same reasoning that pins a dictionary stream to ZLIB.
// deflateReset() clears the path, so this has to run per call, not at init.
//
// A stream ISA-L has already started is the one case that must not be pinned.
// deflateParams() can lower the level to 0 mid-stream, but by then ISA-L has
// emitted a header plus compressed data and still holds unflushed state, so
// handing the stream to a zlib deflate state that was never fed emits a
// second header and produces output that does not inflate (Z_DATA_ERROR, only
// the pre-switch bytes recoverable). Staying on IGZIP leaves the new level
// unhonored -- output is still valid, round-trippable deflate -- which is the
// same deliberate mid-stream residual as deflate()'s Z_BLOCK -> Z_SYNC_FLUSH
// aliasing. Documented in the README.
if (!IsOffloadableCompressionLevel(deflate_settings->level) &&
!IgzipOwnsDeflateStream(deflate_settings)) {
SetDeflatePath(deflate_settings, ZLIB);
}
int ret = 1;
bool iaa_available = false;
bool qat_available = false;
bool igzip_available = false;
if (!in_call && deflate_settings->path != ZLIB) {
uint32_t input_len = strm->avail_in;
uint32_t output_len = strm->avail_out;
bool igzip_stream_active = false;
#ifdef USE_IAA
iaa_available = (flush == Z_FINISH) && configs[USE_IAA_COMPRESS] &&
SupportedOptionsIAA(deflate_settings->window_bits,
input_len, output_len);
#endif
#ifdef USE_QAT
qat_available =
(flush == Z_FINISH) && configs[USE_QAT_COMPRESS] &&
output_len >= QAT_DEST_BUFFER_MIN_SIZE &&
SupportedOptionsQAT(deflate_settings->window_bits, input_len);
#endif
#ifdef USE_IGZIP
igzip_stream_active = IgzipOwnsDeflateStream(deflate_settings);
igzip_available =
configs[USE_IGZIP_COMPRESS] && SupportedOptionsIGZIPDeflate(flush);
#endif
// If both accelerators are enabled, send configured ratio of requests to
// one or the other
ExecutionPath path_selected = ZLIB;
if (igzip_stream_active) {
path_selected = IGZIP;
} else if (iaa_available && qat_available) {
if (static_cast<uint32_t>(std::rand() % 100) <
configs[IAA_COMPRESS_PERCENTAGE]) {
path_selected = IAA;
} else {
path_selected = QAT;
}
} else if (iaa_available) {
path_selected = IAA;
} else if (qat_available) {
path_selected = QAT;
} else if (igzip_available) {
path_selected = IGZIP;
}
if (path_selected == IAA) {
#ifdef USE_IAA
in_call = true;
// Casting to uint32_t is safe, as IAA is not used for any blocks larger
// than 2MB
uint32_t max_compressed_size = (uint32_t)deflateBound(strm, input_len);
ret = CompressIAA(strm->next_in, &input_len, strm->next_out, &output_len,
qpl_path_hardware, deflate_settings->window_bits,
max_compressed_size);
SetDeflatePath(deflate_settings, IAA);
in_call = false;
INCREMENT_STAT(DEFLATE_IAA_COUNT);
INCREMENT_STAT_COND(ret != 0, DEFLATE_IAA_ERROR_COUNT);
#endif // USE_IAA
} else if (path_selected == QAT) {
#ifdef USE_QAT
in_call = true;
ret = CompressQAT(strm->next_in, &input_len, strm->next_out, &output_len,
deflate_settings->window_bits);
SetDeflatePath(deflate_settings, QAT);
in_call = false;
INCREMENT_STAT(DEFLATE_QAT_COUNT);
INCREMENT_STAT_COND(ret != 0, DEFLATE_QAT_ERROR_COUNT);
#endif // USE_QAT
} else if (path_selected == IGZIP) {
#ifdef USE_IGZIP
if (deflate_settings->isal_strm == nullptr) {
deflate_settings->isal_strm = InitCompressIGZIP(
deflate_settings->level, deflate_settings->window_bits);
}
if (deflate_settings->isal_strm != nullptr) {
in_call = true;
ret = CompressIGZIP(deflate_settings->isal_strm, flush, strm->next_in,
&input_len, strm->next_out, &output_len,
&strm->total_in, &strm->total_out);
SetDeflatePath(deflate_settings, IGZIP);
in_call = false;
INCREMENT_STAT(DEFLATE_IGZIP_COUNT);
INCREMENT_STAT_COND(ret != 0, DEFLATE_IGZIP_ERROR_COUNT);
}
#endif
}
#ifdef USE_IGZIP
// Accelerator->IGZIP fallback: if IAA or QAT failed and IGZIP is
// available, retry with IGZIP before falling through to software zlib.
if ((path_selected == IAA || path_selected == QAT) && ret != 0 &&
configs[IGZIP_FALLBACK] && igzip_available) {
// Accelerator may have modified input_len/output_len on failure.
// Restore them before retrying with IGZIP.
input_len = strm->avail_in;
output_len = strm->avail_out;
if (deflate_settings->isal_strm == nullptr) {
deflate_settings->isal_strm = InitCompressIGZIP(
deflate_settings->level, deflate_settings->window_bits);
}
if (deflate_settings->isal_strm != nullptr) {
in_call = true;
ret = CompressIGZIP(deflate_settings->isal_strm, flush, strm->next_in,
&input_len, strm->next_out, &output_len,
&strm->total_in, &strm->total_out);
SetDeflatePath(deflate_settings, IGZIP);
in_call = false;
path_selected = IGZIP; // use IGZIP return-code semantics below
INCREMENT_STAT(DEFLATE_IGZIP_COUNT);
INCREMENT_STAT_COND(ret != 0, DEFLATE_IGZIP_ERROR_COUNT);
}
}
#endif // USE_IGZIP accelerator fallback
if (ret == 0) {
strm->next_in += input_len;
strm->avail_in -= input_len;
strm->total_in += input_len;
strm->next_out += output_len;
strm->avail_out -= output_len;
strm->total_out += output_len;
if (path_selected == IGZIP) {
const bool no_progress = (input_len == 0 && output_len == 0);
bool finish_done = false;
#ifdef USE_IGZIP
finish_done = (flush == Z_FINISH) &&
IsIGZIPDeflateFinished(deflate_settings->isal_strm);
#endif
if (finish_done) {
ret = Z_STREAM_END;
} else if (!no_progress) {
ret = Z_OK;
} else {
ret = Z_BUF_ERROR;
}
} else {
if (strm->avail_in == 0) {
ret = Z_STREAM_END;
} else {
ret = Z_BUF_ERROR;
}
}
// Remember the completion the accelerator just reported: zlib's own
// deflate state was never fed, so nothing else records that this stream
// is finished.
if (ret == Z_STREAM_END) {
deflate_settings->stream_end_reached = true;
}
Log(LogLevel::LOG_INFO, "deflate Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", accelerator return code ", ret,
", bytes_in ", input_len, ", bytes_out ", output_len, ", avail_in ",
strm->avail_in, ", avail_out ", strm->avail_out, ", path ",
static_cast<int>(deflate_settings->path), ", path_name ",
ExecutionPathName(deflate_settings->path), "\n");
return ret;
}
}
if (in_call || configs[USE_ZLIB_COMPRESS] || deflate_settings->path == ZLIB) {
// Distinguish "no zlib to delegate to" from "zlib rejected the data": the
// former is an unusable library, not a data problem.
if (orig_deflate == nullptr) {
ret = Z_VERSION_ERROR;
} else {
ret = orig_deflate(strm, flush);
INCREMENT_STAT(DEFLATE_ZLIB_COUNT);
if (!in_call) {
SetDeflatePath(deflate_settings, ZLIB);
}
}
} else {
ret = Z_DATA_ERROR;
}
Log(LogLevel::LOG_INFO, "deflate Line ", __LINE__, ", strm ",
static_cast<void*>(strm), ", zlib return code ", ret, ", avail_in ",
strm->avail_in, ", avail_out ", strm->avail_out, ", path ",
static_cast<int>(deflate_settings->path), ", path_name ",
ExecutionPathName(deflate_settings->path), "\n");
INCREMENT_STAT_COND(ret < 0, DEFLATE_ERROR_COUNT);
return ret;
}
int ZEXPORT deflateEnd(z_streamp strm) {
Log(LogLevel::LOG_INFO, "deflateEnd Line ", __LINE__, ", strm ",
static_cast<void*>(strm), "\n");
auto deflate_settings = deflate_stream_settings.Get(strm);
ReleaseDeflateIgzipState(deflate_settings);
deflate_stream_settings.Unset(strm);
return orig_deflateEnd != nullptr ? orig_deflateEnd(strm) : Z_VERSION_ERROR;
}
// zlib builds deflateReset() on top of deflateResetKeep(), so on a libz whose
// internal calls are interposable this wrapper runs nested inside
// orig_deflateReset(). Acting only after the original returns keeps the outer
// wrapper's state work last, so the two orderings agree. Same shape as
// deflateParams() and inflateReset2().
int ZEXPORT deflateReset(z_streamp strm) {
Log(LogLevel::LOG_INFO, "deflateReset Line ", __LINE__, ", strm ",
static_cast<void*>(strm), "\n");
if (orig_deflateReset == nullptr) {
return Z_VERSION_ERROR;
}
const int ret = orig_deflateReset(strm);
if (ret == Z_OK) {
ResetDeflateStreamState(deflate_stream_settings.Get(strm));
}
return ret;
}
// The other entry point that restarts a finished stream: deflateReset() is
// deflateResetKeep() plus lm_init(), so an application can reach it directly
// and a terminal state left set here would wedge the stream at Z_STREAM_END.
// What it keeps -- the LZ77 window and hash -- only affects how zlib would
// encode the next stream, not whether the shim may offload it: an offloaded
// stream emits no back-references into the previous one, which is a
// self-contained stream any decoder accepts. So no path pin here, unlike
// inflateResetKeep().
int ZEXPORT deflateResetKeep(z_streamp strm) {
Log(LogLevel::LOG_INFO, "deflateResetKeep Line ", __LINE__, ", strm ",
static_cast<void*>(strm), "\n");
if (orig_deflateResetKeep == nullptr) {
return Z_VERSION_ERROR;
}
const int ret = orig_deflateResetKeep(strm);
if (ret == Z_OK) {
ResetDeflateStreamState(deflate_stream_settings.Get(strm));
}
return ret;
}
int ZEXPORT deflateCopy(z_streamp dest, z_streamp source) {
Log(LogLevel::LOG_INFO, "deflateCopy Line ", __LINE__, ", dest ",
static_cast<void*>(dest), ", source ", static_cast<void*>(source), "\n");
auto deflate_settings = deflate_stream_settings.Get(source);
// Refuse while ISA-L owns the source stream and has not finished it. A
// finished stream is copyable: ISA-L is at ZSTATE_END with all output
// delivered, so there is no state left to duplicate, and the terminal state
// the copy inherits answers every deflate() on it -- the same handling QAT
// and IAA already get. zlib's copy duplicates only the zlib deflate state,
// which on an offloaded stream has never been fed, and ISA-L's state cannot
// be duplicated alongside it: isal_zstream::level_buf is cast to a private
// struct holding pointers into its own allocation, so a byte copy would leave
// both streams writing into one pending block. Draining that block first is
// no help -- those bytes belong to the prefix the two streams share, and
// deflateCopy() cannot hand bytes back to the caller. Failing before
// orig_deflateCopy leaves dest as the caller passed it, the same shape as