Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exposed window buffer mode #3017

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/common/zstd_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ MEM_STATIC size_t ZSTD_limitCopy(void* dst, size_t dstCapacity, const void* src,
/* Controls whether the input/output buffer is buffered or stable. */
typedef enum {
ZSTD_bm_buffered = 0, /* Buffer the input/output */
ZSTD_bm_stable = 1 /* ZSTD_inBuffer/ZSTD_outBuffer is stable */
ZSTD_bm_stable = 1, /* ZSTD_inBuffer/ZSTD_outBuffer is stable */
ZSTD_bm_expose = 2 /* Set ZSTD_outBuffer to internal window */
} ZSTD_bufferMode_e;


Expand Down
84 changes: 54 additions & 30 deletions lib/decompress/zstd_decompress.c
Original file line number Diff line number Diff line change
Expand Up @@ -1708,9 +1708,9 @@ ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam)
bounds.upperBound = (int)ZSTD_f_zstd1_magicless;
ZSTD_STATIC_ASSERT(ZSTD_f_zstd1 < ZSTD_f_zstd1_magicless);
return bounds;
case ZSTD_d_stableOutBuffer:
case ZSTD_d_outBufferMode:
bounds.lowerBound = (int)ZSTD_bm_buffered;
bounds.upperBound = (int)ZSTD_bm_stable;
bounds.upperBound = (int)ZSTD_bm_expose;
return bounds;
case ZSTD_d_forceIgnoreChecksum:
bounds.lowerBound = (int)ZSTD_d_validateChecksum;
Expand Down Expand Up @@ -1751,7 +1751,7 @@ size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value
case ZSTD_d_format:
*value = (int)dctx->format;
return 0;
case ZSTD_d_stableOutBuffer:
case ZSTD_d_outBufferMode:
*value = (int)dctx->outBufferMode;
return 0;
case ZSTD_d_forceIgnoreChecksum:
Expand All @@ -1778,8 +1778,8 @@ size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value
CHECK_DBOUNDS(ZSTD_d_format, value);
dctx->format = (ZSTD_format_e)value;
return 0;
case ZSTD_d_stableOutBuffer:
CHECK_DBOUNDS(ZSTD_d_stableOutBuffer, value);
case ZSTD_d_outBufferMode:
CHECK_DBOUNDS(ZSTD_d_outBufferMode, value);
dctx->outBufferMode = (ZSTD_bufferMode_e)value;
return 0;
case ZSTD_d_forceIgnoreChecksum:
Expand Down Expand Up @@ -1873,22 +1873,30 @@ static int ZSTD_DCtx_isOversizedTooLong(ZSTD_DStream* zds)
return zds->oversizedDuration >= ZSTD_WORKSPACETOOLARGE_MAXDURATION;
}

/* Checks that the output buffer hasn't changed if ZSTD_obm_stable is used. */
/* Checks that the output buffer hasn't changed if ZSTD_bm_stable is used. */
static size_t ZSTD_checkOutBuffer(ZSTD_DStream const* zds, ZSTD_outBuffer const* output)
{
ZSTD_outBuffer const expect = zds->expectedOutBuffer;
/* No requirement when ZSTD_obm_stable is not enabled. */
if (zds->outBufferMode != ZSTD_bm_stable)
/* No requirement when ZSTD_bm_buffered is enabled (default). */
if (zds->outBufferMode == ZSTD_bm_buffered)
return 0;
/* Any buffer is allowed in zdss_init, this must be the same for every other call until
/* With ZSTD_bm_expose, output must be cleared because it will be set only on buffer flush.
Clearing output is not strictly necessary but adds extrra safety against user error. */
if (zds->outBufferMode == ZSTD_bm_expose) {
if(output->dst || output->size)
RETURN_ERROR(dstBuffer_wrong, "must acknowledge output by clearing dst and size");
return 0;
}
/* ZSTD_bm_stable:
* Any buffer is allowed in zdss_init, this must be the same for every other call until
* the context is reset.
*/
if (zds->streamStage == zdss_init)
return 0;
/* The buffer must match our expectation exactly. */
if (expect.dst == output->dst && expect.pos == output->pos && expect.size == output->size)
return 0;
RETURN_ERROR(dstBuffer_wrong, "ZSTD_d_stableOutBuffer enabled but output differs!");
RETURN_ERROR(dstBuffer_wrong, "ZSTD_d_outBufferMode == ZSTD_bufmode_stable but output differs!");
}

/* Calls ZSTD_decompressContinue() with the right parameters for ZSTD_decompressStream()
Expand All @@ -1900,7 +1908,7 @@ static size_t ZSTD_decompressContinueStream(
ZSTD_DStream* zds, char** op, char* oend,
void const* src, size_t srcSize) {
int const isSkipFrame = ZSTD_isSkipFrame(zds);
if (zds->outBufferMode == ZSTD_bm_buffered) {
if (zds->outBufferMode != ZSTD_bm_stable) {
size_t const dstSize = isSkipFrame ? 0 : zds->outBuffSize - zds->outStart;
size_t const decodedSize = ZSTD_decompressContinue(zds,
zds->outBuff + zds->outStart, dstSize, src, srcSize);
Expand Down Expand Up @@ -1944,7 +1952,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
"forbidden. in: pos: %u vs size: %u",
(U32)input->pos, (U32)input->size);
RETURN_ERROR_IF(
output->pos > output->size,
output->pos > output->size && zds->outBufferMode != ZSTD_bm_expose,
dstSize_tooSmall,
"forbidden. out: pos: %u vs size: %u",
(U32)output->pos, (U32)output->size);
Expand Down Expand Up @@ -1991,6 +1999,8 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
DEBUGLOG(5, "ZSTD_decompressStream: detected legacy version v0.%u", legacyVersion);
RETURN_ERROR_IF(zds->staticSize, memory_allocation,
"legacy support is incompatible with static dctx");
RETURN_ERROR_IF(zds->outBufferMode == ZSTD_bm_expose, parameter_unsupported,
"legacy support is incompatible with ZSTD_bufmode_expose");
FORWARD_IF_ERROR(ZSTD_initLegacyStream(&zds->legacyContext,
zds->previousLegacyVersion, legacyVersion,
dict, dictSize), "");
Expand Down Expand Up @@ -2022,6 +2032,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
/* check for single-pass mode opportunity */
if (zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
&& zds->fParams.frameType != ZSTD_skippableFrame
&& zds->outBufferMode != ZSTD_bm_expose
&& (U64)(size_t)(oend-op) >= zds->fParams.frameContentSize) {
size_t const cSize = ZSTD_findFrameCompressedSize(istart, (size_t)(iend-istart));
if (cSize <= (size_t)(iend-istart)) {
Expand All @@ -2037,12 +2048,12 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
break;
} }

/* Check output buffer is large enough for ZSTD_odm_stable. */
/* Check output buffer is large enough for ZSTD_bm_stable. */
if (zds->outBufferMode == ZSTD_bm_stable
&& zds->fParams.frameType != ZSTD_skippableFrame
&& zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
&& (U64)(size_t)(oend-op) < zds->fParams.frameContentSize) {
RETURN_ERROR(dstSize_tooSmall, "ZSTD_obm_stable passed but ZSTD_outBuffer is too small");
RETURN_ERROR(dstSize_tooSmall, "ZSTD_bm_stable passed but ZSTD_outBuffer is too small");
}

/* Consume header (see ZSTDds_decodeFrameHeader) */
Expand All @@ -2068,7 +2079,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB

/* Adapt buffer sizes to frame header instructions */
{ size_t const neededInBuffSize = MAX(zds->fParams.blockSizeMax, 4 /* frame checksum */);
size_t const neededOutBuffSize = zds->outBufferMode == ZSTD_bm_buffered
size_t const neededOutBuffSize = zds->outBufferMode != ZSTD_bm_stable
? ZSTD_decodingBufferSize_min(zds->fParams.windowSize, zds->fParams.frameContentSize)
: 0;

Expand Down Expand Up @@ -2149,23 +2160,36 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
}
case zdss_flush:
{ size_t const toFlushSize = zds->outEnd - zds->outStart;
size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize);
size_t flushedSize;
if (zds->outBufferMode == ZSTD_bm_expose)
{
output->dst = op = zds->outBuff + zds->outStart;
output->size = toFlushSize;
zds->streamStage = zdss_flushdone;
zds->outStart += toFlushSize;
someMoreWork = 0;
break;
}
flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize);
op += flushedSize;
zds->outStart += flushedSize;
if (flushedSize == toFlushSize) { /* flush completed */
zds->streamStage = zdss_read;
if ( (zds->outBuffSize < zds->fParams.frameContentSize)
&& (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) {
DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)",
(int)(zds->outBuffSize - zds->outStart),
(U32)zds->fParams.blockSizeMax);
zds->outStart = zds->outEnd = 0;
}
if (flushedSize != toFlushSize) {
/* cannot complete flush */
someMoreWork = 0;
break;
} }
/* cannot complete flush */
someMoreWork = 0;
break;
}
}
ZSTD_FALLTHROUGH;
case zdss_flushdone: /* flush completed */
zds->streamStage = zdss_read;
if ( (zds->outBuffSize < zds->fParams.frameContentSize)
&& (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) {
DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)",
(int)(zds->outBuffSize - zds->outStart),
(U32)zds->fParams.blockSizeMax);
zds->outStart = zds->outEnd = 0;
}
break;

default:
assert(0); /* impossible */
Expand All @@ -2176,7 +2200,7 @@ size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inB
input->pos = (size_t)(ip - (const char*)(input->src));
output->pos = (size_t)(op - (char*)(output->dst));

/* Update the expected output buffer for ZSTD_obm_stable. */
/* Update the expected output buffer for ZSTD_bm_stable. */
zds->expectedOutBuffer = *output;

if ((ip==istart) && (op==ostart)) { /* no forward progress */
Expand Down
2 changes: 1 addition & 1 deletion lib/decompress/zstd_decompress_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ typedef enum { ZSTDds_getFrameHeaderSize, ZSTDds_decodeFrameHeader,
ZSTDds_decodeSkippableHeader, ZSTDds_skipFrame } ZSTD_dStage;

typedef enum { zdss_init=0, zdss_loadHeader,
zdss_read, zdss_load, zdss_flush } ZSTD_dStreamStage;
zdss_read, zdss_load, zdss_flush, zdss_flushdone } ZSTD_dStreamStage;

typedef enum {
ZSTD_use_indefinitely = -1, /* Use the dictionary indefinitely */
Expand Down
84 changes: 53 additions & 31 deletions lib/zstd.h
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ typedef enum {
* within the experimental section of the API.
* At the time of this writing, they include :
* ZSTD_d_format
* ZSTD_d_stableOutBuffer
* ZSTD_d_outBufferMode
* ZSTD_d_forceIgnoreChecksum
* ZSTD_d_refMultipleDDicts
* Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them.
Expand Down Expand Up @@ -1321,6 +1321,52 @@ typedef enum {
ZSTD_ps_disable = 2 /* Do not use the feature */
} ZSTD_paramSwitch_e;

typedef enum {
/* Default. Let the (de)compressor allocate internal input/output buffers as needed. */
ZSTD_bufmode_buffered = 0,

/* Tells the (de)compressor that the ZSTD_outBuffer will ALWAYS be the same
* between calls, except for the modifications that zstd makes to pos (the
* caller must not modify pos). This is checked by the decompressor, and
* decompression will fail if it ever changes. Therefore the ZSTD_outBuffer
* MUST be large enough to fit the entire decompressed frame. This will be
* checked when the frame content size is known.The data in the ZSTD_outBuffer
* in the range[dst, dst + pos) MUST not be modified during decompression
* or you will get data corruption.
*
* When this flags is enabled zstd won't allocate an output buffer, because
* it can write directly to the ZSTD_outBuffer, but it will still allocate
* an input buffer large enough to fit any compressed block.This will also
* avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer.
* If you need to avoid the input buffer allocation use the buffer-less
* streaming API.
*
* NOTE: So long as the ZSTD_outBuffer always points to valid memory, using
* this flag is ALWAYS memory safe, and will never access out -of-bounds
* memory. However, decompression WILL fail if you violate the preconditions.
*
* WARNING: The data in the ZSTD_outBuffer in the range[dst, dst + pos) MUST
* not be modified during decompression or you will get data corruption. This
* is because zstd needs to reference data in the ZSTD_outBuffer to regenerate
* matches. Normally zstd maintains its own buffer for this purpose, but passing
* this flag tells zstd to use the user provided buffer. */
ZSTD_bufmode_stable = 1,

/* Tells the decompressor to not copy its internal buffer.
* Instead, once output data are available, the ZSTD_outBuffer's size and dst
* fields are updated so that dst[0..size) is a view into the internal
* decompressor window; no copy operation takes place.
* The caller must zero the ZSTD_outBuffer size and dst fields to acknowledge
* that the data were processed before continuing decompression.
* Not doing so will assert.
* The data stay valid until the next call into the decompressor.
* The window data must NOT be modified or you will get data corruption.
* NOTE: The ZSTD_outBuffer's pos field is not used and should be 0.
* This mode is not compatible with legacy streams and is currently
* only supported for decompression. */
ZSTD_bufmode_expose = 2,
} ZSTD_bufmode_e;

/***************************************
* Frame size functions
***************************************/
Expand Down Expand Up @@ -1862,7 +1908,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const vo
* Experimental parameter.
* Default is 0 == disabled. Set to 1 to enable.
*
* Tells he compressor that the ZSTD_outBuffer will not be resized between
* Tells the compressor that the ZSTD_outBuffer will not be resized between
* calls. Specifically: (out.size - out.pos) will never grow. This gives the
* compressor the freedom to say: If the compressed data doesn't fit in the
* output buffer then return ZSTD_error_dstSizeTooSmall. This allows us to
Expand Down Expand Up @@ -2092,37 +2138,13 @@ ZSTDLIB_STATIC_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParamete
* allowing selection between ZSTD_format_e input compression formats
*/
#define ZSTD_d_format ZSTD_d_experimentalParam1
/* ZSTD_d_stableOutBuffer

/* ZSTD_d_outBufferMode
* Experimental parameter.
* Default is 0 == disabled. Set to 1 to enable.
*
* Tells the decompressor that the ZSTD_outBuffer will ALWAYS be the same
* between calls, except for the modifications that zstd makes to pos (the
* caller must not modify pos). This is checked by the decompressor, and
* decompression will fail if it ever changes. Therefore the ZSTD_outBuffer
* MUST be large enough to fit the entire decompressed frame. This will be
* checked when the frame content size is known. The data in the ZSTD_outBuffer
* in the range [dst, dst + pos) MUST not be modified during decompression
* or you will get data corruption.
*
* When this flags is enabled zstd won't allocate an output buffer, because
* it can write directly to the ZSTD_outBuffer, but it will still allocate
* an input buffer large enough to fit any compressed block. This will also
* avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer.
* If you need to avoid the input buffer allocation use the buffer-less
* streaming API.
*
* NOTE: So long as the ZSTD_outBuffer always points to valid memory, using
* this flag is ALWAYS memory safe, and will never access out-of-bounds
* memory. However, decompression WILL fail if you violate the preconditions.
*
* WARNING: The data in the ZSTD_outBuffer in the range [dst, dst + pos) MUST
* not be modified during decompression or you will get data corruption. This
* is because zstd needs to reference data in the ZSTD_outBuffer to regenerate
* matches. Normally zstd maintains its own buffer for this purpose, but passing
* this flag tells zstd to use the user provided buffer.
* Default is ZSTD_bufmode_buffered.
* Param has values of type ZSTD_bufmode_e
*/
#define ZSTD_d_stableOutBuffer ZSTD_d_experimentalParam2
#define ZSTD_d_outBufferMode ZSTD_d_experimentalParam2

/* ZSTD_d_forceIgnoreChecksum
* Experimental parameter.
Expand Down
Loading