1 // SPDX-License-Identifier: 0BSD
3 ///////////////////////////////////////////////////////////////////////////////
6 /// \brief Output queue handling in multithreaded coding
8 // Author: Lasse Collin
10 ///////////////////////////////////////////////////////////////////////////////
15 /// Get the maximum number of buffers that may be allocated based
16 /// on the number of threads. For now this is twice the number of threads.
17 /// It's a compromise between RAM usage and keeping the worker threads busy
18 /// when buffers finish out of order.
19 #define GET_BUFS_LIMIT(threads) (2 * (threads))
23 lzma_outq_memusage(uint64_t buf_size_max
, uint32_t threads
)
25 // This is to ease integer overflow checking: We may allocate up to
26 // GET_BUFS_LIMIT(LZMA_THREADS_MAX) buffers and we need some extra
27 // memory for other data structures too (that's the /2).
29 // lzma_outq_prealloc_buf() will still accept bigger buffers than this.
31 = UINT64_MAX
/ GET_BUFS_LIMIT(LZMA_THREADS_MAX
) / 2;
33 if (threads
> LZMA_THREADS_MAX
|| buf_size_max
> limit
)
36 return GET_BUFS_LIMIT(threads
)
37 * lzma_outq_outbuf_memusage(buf_size_max
);
42 move_head_to_cache(lzma_outq
*outq
, const lzma_allocator
*allocator
)
44 assert(outq
->head
!= NULL
);
45 assert(outq
->tail
!= NULL
);
46 assert(outq
->bufs_in_use
> 0);
48 lzma_outbuf
*buf
= outq
->head
;
49 outq
->head
= buf
->next
;
50 if (outq
->head
== NULL
)
53 if (outq
->cache
!= NULL
&& outq
->cache
->allocated
!= buf
->allocated
)
54 lzma_outq_clear_cache(outq
, allocator
);
56 buf
->next
= outq
->cache
;
60 outq
->mem_in_use
-= lzma_outq_outbuf_memusage(buf
->allocated
);
67 free_one_cached_buffer(lzma_outq
*outq
, const lzma_allocator
*allocator
)
69 assert(outq
->cache
!= NULL
);
71 lzma_outbuf
*buf
= outq
->cache
;
72 outq
->cache
= buf
->next
;
74 --outq
->bufs_allocated
;
75 outq
->mem_allocated
-= lzma_outq_outbuf_memusage(buf
->allocated
);
77 lzma_free(buf
, allocator
);
83 lzma_outq_clear_cache(lzma_outq
*outq
, const lzma_allocator
*allocator
)
85 while (outq
->cache
!= NULL
)
86 free_one_cached_buffer(outq
, allocator
);
93 lzma_outq_clear_cache2(lzma_outq
*outq
, const lzma_allocator
*allocator
,
96 if (outq
->cache
== NULL
)
100 while (outq
->cache
->next
!= NULL
)
101 free_one_cached_buffer(outq
, allocator
);
103 // Free the last one only if its size doesn't equal to keep_size.
104 if (outq
->cache
->allocated
!= keep_size
)
105 free_one_cached_buffer(outq
, allocator
);
112 lzma_outq_init(lzma_outq
*outq
, const lzma_allocator
*allocator
,
115 if (threads
> LZMA_THREADS_MAX
)
116 return LZMA_OPTIONS_ERROR
;
118 const uint32_t bufs_limit
= GET_BUFS_LIMIT(threads
);
121 while (outq
->head
!= NULL
)
122 move_head_to_cache(outq
, allocator
);
124 // If new buf_limit is lower than the old one, we may need to free
125 // a few cached buffers.
126 while (bufs_limit
< outq
->bufs_allocated
)
127 free_one_cached_buffer(outq
, allocator
);
129 outq
->bufs_limit
= bufs_limit
;
137 lzma_outq_end(lzma_outq
*outq
, const lzma_allocator
*allocator
)
139 while (outq
->head
!= NULL
)
140 move_head_to_cache(outq
, allocator
);
142 lzma_outq_clear_cache(outq
, allocator
);
148 lzma_outq_prealloc_buf(lzma_outq
*outq
, const lzma_allocator
*allocator
,
151 // Caller must have checked it with lzma_outq_has_buf().
152 assert(outq
->bufs_in_use
< outq
->bufs_limit
);
154 // If there already is appropriately-sized buffer in the cache,
155 // we need to do nothing.
156 if (outq
->cache
!= NULL
&& outq
->cache
->allocated
== size
)
159 if (size
> SIZE_MAX
- sizeof(lzma_outbuf
))
160 return LZMA_MEM_ERROR
;
162 const size_t alloc_size
= lzma_outq_outbuf_memusage(size
);
164 // The cache may have buffers but their size is wrong.
165 lzma_outq_clear_cache(outq
, allocator
);
167 outq
->cache
= lzma_alloc(alloc_size
, allocator
);
168 if (outq
->cache
== NULL
)
169 return LZMA_MEM_ERROR
;
171 outq
->cache
->next
= NULL
;
172 outq
->cache
->allocated
= size
;
174 ++outq
->bufs_allocated
;
175 outq
->mem_allocated
+= alloc_size
;
182 lzma_outq_get_buf(lzma_outq
*outq
, void *worker
)
184 // Caller must have used lzma_outq_prealloc_buf() to ensure these.
185 assert(outq
->bufs_in_use
< outq
->bufs_limit
);
186 assert(outq
->bufs_in_use
< outq
->bufs_allocated
);
187 assert(outq
->cache
!= NULL
);
189 lzma_outbuf
*buf
= outq
->cache
;
190 outq
->cache
= buf
->next
;
193 if (outq
->tail
!= NULL
) {
194 assert(outq
->head
!= NULL
);
195 outq
->tail
->next
= buf
;
197 assert(outq
->head
== NULL
);
203 buf
->worker
= worker
;
204 buf
->finished
= false;
205 buf
->finish_ret
= LZMA_STREAM_END
;
207 buf
->decoder_in_pos
= 0;
209 buf
->unpadded_size
= 0;
210 buf
->uncompressed_size
= 0;
213 outq
->mem_in_use
+= lzma_outq_outbuf_memusage(buf
->allocated
);
220 lzma_outq_is_readable(const lzma_outq
*outq
)
222 if (outq
->head
== NULL
)
225 return outq
->read_pos
< outq
->head
->pos
|| outq
->head
->finished
;
230 lzma_outq_read(lzma_outq
*restrict outq
,
231 const lzma_allocator
*restrict allocator
,
232 uint8_t *restrict out
, size_t *restrict out_pos
,
234 lzma_vli
*restrict unpadded_size
,
235 lzma_vli
*restrict uncompressed_size
)
237 // There must be at least one buffer from which to read.
238 if (outq
->bufs_in_use
== 0)
242 lzma_outbuf
*buf
= outq
->head
;
244 // Copy from the buffer to output.
246 // FIXME? In threaded decoder it may be bad to do this copy while
247 // the mutex is being held.
248 lzma_bufcpy(buf
->buf
, &outq
->read_pos
, buf
->pos
,
249 out
, out_pos
, out_size
);
251 // Return if we didn't get all the data from the buffer.
252 if (!buf
->finished
|| outq
->read_pos
< buf
->pos
)
255 // The buffer was finished. Tell the caller its size information.
256 if (unpadded_size
!= NULL
)
257 *unpadded_size
= buf
->unpadded_size
;
259 if (uncompressed_size
!= NULL
)
260 *uncompressed_size
= buf
->uncompressed_size
;
262 // Remember the return value.
263 const lzma_ret finish_ret
= buf
->finish_ret
;
265 // Free this buffer for further use.
266 move_head_to_cache(outq
, allocator
);
274 lzma_outq_enable_partial_output(lzma_outq
*outq
,
275 void (*enable_partial_output
)(void *worker
))
277 if (outq
->head
!= NULL
&& !outq
->head
->finished
278 && outq
->head
->worker
!= NULL
) {
279 enable_partial_output(outq
->head
->worker
);
281 // Set it to NULL since calling it twice is pointless.
282 outq
->head
->worker
= NULL
;