From 6220c23c710320d8b14a6ac1697481c8998132ad Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sun, 10 May 2026 16:20:51 -0700 Subject: [PATCH 01/11] Added Swift's ".build" folder to the ignore list. This is generated (by default) when building with `swift` (the Swift CLI), e.g. `swift build`. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 03ecbde6af8..91aff3ee698 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ build-* cmakebuild/ cmake-build/ +# Swift build artefacts +.build/ + # Test artefacts tmp* *.zst From e256560a6081a1e8cb9643c5433bac607551b02d Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sun, 10 May 2026 16:22:37 -0700 Subject: [PATCH 02/11] Added ".swiftpm/xcode/" to the ignore list. This folder contains generated Xcode project files that are used locally in some circumstances but should never be checked in (they're derived from Package.swift). --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 91aff3ee698..ca36eb38e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ cmake-build/ # Swift build artefacts .build/ +.swiftpm/xcode/ # Test artefacts tmp* From 3599d36c11e44d1334f979c6d09bab33b0032707 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Tue, 12 May 2026 10:12:31 -0700 Subject: [PATCH 03/11] Added the `Zstd` Swift module, with a nicer API (for Swift) than the existing `libzstd`. The new module exposes the same fundamental API but with names that are cleaner and more idiomatic in Swift, and with some key additional Swift-specific annotations (such as on closed enums, to make them import as Swift enums rather than just free-floating constants). `libzstd` remains unchanged for backwards compatibility (or those that prefer the "raw" C API, I suppose). Note that this stops short of actual code changes or API changes beyond just renames (e.g. it does not change the parameter orders to be more idiomatic in Swift, nor present the various structs as actual Swift structs or classes). It _could_ - it could include a whole shim layer, written in C and/or Swift, to make the Swift API look like whatever we want - but it's a conscious choice to not go that far at this time. There are existing Swift packages which wrap this library and perform more substantial API changes. --- Package.swift | 67 +++- Sources/Zstd/_empty.c | 1 + Sources/Zstd/include/Zstd.apinotes | 389 +++++++++++++++++++ Sources/Zstd/include/Zstd_module.h | 81 ++++ Sources/Zstd/include/module.modulemap | 11 + Sources/libzstd/_empty.c | 1 + Sources/libzstd/include/libzstd_module.h | 10 + Sources/libzstd/include/module.modulemap | 10 + Sources/zstd-example/ZstdExample.swift | 455 +++++++++++++++++++++++ Sources/zstd-example/dump-api.sh | 61 +++ lib/module.modulemap | 5 +- lib/zstd.h | 91 +++-- lib/zstd_errors.h | 2 +- 13 files changed, 1136 insertions(+), 48 deletions(-) create mode 100644 Sources/Zstd/_empty.c create mode 100644 Sources/Zstd/include/Zstd.apinotes create mode 100644 Sources/Zstd/include/Zstd_module.h create mode 100644 Sources/Zstd/include/module.modulemap create mode 100644 Sources/libzstd/_empty.c create mode 100644 Sources/libzstd/include/libzstd_module.h create mode 100644 Sources/libzstd/include/module.modulemap create mode 100644 Sources/zstd-example/ZstdExample.swift create mode 100755 Sources/zstd-example/dump-api.sh diff --git a/Package.swift b/Package.swift index 97f2c6a5251..cf33c6f5240 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,29 +6,66 @@ import PackageDescription let package = Package( name: "zstd", platforms: [ - .macOS(.v10_10), .iOS(.v9), .tvOS(.v9) + .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6) ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "libzstd", - targets: [ "libzstd" ]) - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + // Two library products. Each surfaces only one Swift module to its consumers: + // + // • `Zstd` — modern API (renamed functions, prefix-stripped enum cases). + // • `libzstd` — legacy API (raw `ZSTD_*` names) for source-level backwards + // compatibility with code that predates the rename. + // + // A consumer that depends on the `Zstd` product can NOT `import libzstd` + // (and vice versa), because each product is backed by a separate facade + // target whose modulemap declares only one of the two modules. + .library(name: "Zstd", targets: [ "Zstd" ]), + .library(name: "libzstd", targets: [ "libzstd" ]), + .executable(name: "zstd-example", targets: [ "zstd-example" ]), ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. + // ── The actual C library ──────────────────────────────────────────── + // Compiles the real zstd sources once and exposes the raw headers via + // a "private" `_ZstdCore` module that nobody is expected to import. + // Both facade targets below depend on this one for the C symbols. .target( - name: "libzstd", + name: "_ZstdCore", path: "lib", sources: [ "common", "compress", "decompress", "dictBuilder" ], publicHeadersPath: ".", cSettings: [ - .headerSearchPath(".") - ]) + .headerSearchPath("."), + ]), + + // ── Modern Swift facade ───────────────────────────────────────────── + // Owns the `Zstd_module.h` umbrella (which defines + // `ZSTD_FOR_SWIFT_MODERN_API` before including the real headers) and + // its accompanying `Zstd.apinotes`. The umbrella + apinotes pair is the + // only thing that turns "raw zstd headers" into the modern Swift API. + .target( + name: "Zstd", + dependencies: [ "_ZstdCore" ], + path: "Sources/Zstd", + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("../../lib"), // so the umbrella can find zstd.h + ]), + + // ── Legacy Swift facade ───────────────────────────────────────────── + // Same headers as `Zstd`, but with an umbrella that doesn't define the + // modern-API macro and no apinotes file, so the raw C names come through. + .target( + name: "libzstd", + dependencies: [ "_ZstdCore" ], + path: "Sources/libzstd", + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("../../lib"), + ]), + + .executableTarget( + name: "zstd-example", + dependencies: [ "Zstd" ], + path: "Sources/zstd-example"), ], swiftLanguageVersions: [.v5], cLanguageStandard: .gnu11, diff --git a/Sources/Zstd/_empty.c b/Sources/Zstd/_empty.c new file mode 100644 index 00000000000..8a4dfb92063 --- /dev/null +++ b/Sources/Zstd/_empty.c @@ -0,0 +1 @@ +// SwiftPM requires at least one source file per C target. diff --git a/Sources/Zstd/include/Zstd.apinotes b/Sources/Zstd/include/Zstd.apinotes new file mode 100644 index 00000000000..caf856fbd40 --- /dev/null +++ b/Sources/Zstd/include/Zstd.apinotes @@ -0,0 +1,389 @@ +--- +Name: Zstd + +# ─── Type renames ──────────────────────────────────────────────────────────── +# C struct tags (the underlying `_s` suffixed names) and their corresponding +# typedef aliases both need an entry — the Swift importer surfaces both. +Tags: + - Name: ZSTD_inBuffer_s + SwiftName: 'InputBuffer' + - Name: ZSTD_outBuffer_s + SwiftName: 'OutputBuffer' + - Name: ZSTD_bounds + SwiftName: 'ParameterBounds' + +Typedefs: + - Name: ZSTD_inBuffer + SwiftName: 'InputBuffer' + - Name: ZSTD_outBuffer + SwiftName: 'OutputBuffer' + - Name: ZSTD_bounds + SwiftName: 'ParameterBounds' + - Name: ZDICT_params_t + SwiftName: 'DictionaryTrainingParameters' + - Name: ZSTD_strategy + SwiftName: 'Strategy' + - Name: ZSTD_cParameter + SwiftName: 'CompressionParameter' + - Name: ZSTD_dParameter + SwiftName: 'DecompressionParameter' + - Name: ZSTD_ResetDirective + SwiftName: 'ResetDirective' + - Name: ZSTD_EndDirective + SwiftName: 'EndDirective' + - Name: ZSTD_ErrorCode + SwiftName: 'ErrorCode' + +# ─── Enum case renames ─────────────────────────────────────────────────────── +# The clang importer's automatic prefix-stripping only removes `ZSTD_` here — +# it stops at the type-name-shared boundary and won't strip a further `e_`, +# `reset_`, or `error_` sub-prefix. We rename the cases explicitly. +Enumerators: + # CompressionParameter — expand the few remaining abbreviations. + - Name: ZSTD_c_targetCBlockSize + SwiftName: 'targetCompressedBlockSize' + - Name: ZSTD_c_nbWorkers + SwiftName: 'workerCount' + - Name: ZSTD_c_contentSizeFlag + SwiftName: 'embedContentSize' + - Name: ZSTD_c_checksumFlag + SwiftName: 'embedChecksum' + - Name: ZSTD_c_dictIDFlag + SwiftName: 'embedDictionaryID' + - Name: ZSTD_c_experimentalParam1 + SwiftName: 'experimentalParameter1' + - Name: ZSTD_c_experimentalParam2 + SwiftName: 'experimentalParameter2' + - Name: ZSTD_c_experimentalParam3 + SwiftName: 'experimentalParameter3' + - Name: ZSTD_c_experimentalParam4 + SwiftName: 'experimentalParameter4' + - Name: ZSTD_c_experimentalParam5 + SwiftName: 'experimentalParameter5' + - Name: ZSTD_c_experimentalParam7 + SwiftName: 'experimentalParameter7' + - Name: ZSTD_c_experimentalParam8 + SwiftName: 'experimentalParameter8' + - Name: ZSTD_c_experimentalParam9 + SwiftName: 'experimentalParameter9' + - Name: ZSTD_c_experimentalParam10 + SwiftName: 'experimentalParameter10' + - Name: ZSTD_c_experimentalParam11 + SwiftName: 'experimentalParameter11' + - Name: ZSTD_c_experimentalParam12 + SwiftName: 'experimentalParameter12' + - Name: ZSTD_c_experimentalParam13 + SwiftName: 'experimentalParameter13' + - Name: ZSTD_c_experimentalParam14 + SwiftName: 'experimentalParameter14' + - Name: ZSTD_c_experimentalParam15 + SwiftName: 'experimentalParameter15' + - Name: ZSTD_c_experimentalParam16 + SwiftName: 'experimentalParameter16' + - Name: ZSTD_c_experimentalParam17 + SwiftName: 'experimentalParameter17' + - Name: ZSTD_c_experimentalParam18 + SwiftName: 'experimentalParameter18' + - Name: ZSTD_c_experimentalParam19 + SwiftName: 'experimentalParameter19' + - Name: ZSTD_c_experimentalParam20 + SwiftName: 'experimentalParameter20' + + # DecompressionParameter — experimental cases share the `d_experimentalParamN` form. + - Name: ZSTD_d_experimentalParam1 + SwiftName: 'experimentalParameter1' + - Name: ZSTD_d_experimentalParam2 + SwiftName: 'experimentalParameter2' + - Name: ZSTD_d_experimentalParam3 + SwiftName: 'experimentalParameter3' + - Name: ZSTD_d_experimentalParam4 + SwiftName: 'experimentalParameter4' + - Name: ZSTD_d_experimentalParam5 + SwiftName: 'experimentalParameter5' + - Name: ZSTD_d_experimentalParam6 + SwiftName: 'experimentalParameter6' + + # EndDirective + - Name: ZSTD_e_continue + SwiftName: 'continue' + - Name: ZSTD_e_flush + SwiftName: 'flush' + - Name: ZSTD_e_end + SwiftName: 'end' + + # ResetDirective + - Name: ZSTD_reset_session_only + SwiftName: 'sessionOnly' + - Name: ZSTD_reset_parameters + SwiftName: 'parameters' + - Name: ZSTD_reset_session_and_parameters + SwiftName: 'sessionAndParameters' + + # ErrorCode + - Name: ZSTD_error_no_error + SwiftName: 'noError' + - Name: ZSTD_error_GENERIC + SwiftName: 'generic' + - Name: ZSTD_error_prefix_unknown + SwiftName: 'prefixUnknown' + - Name: ZSTD_error_version_unsupported + SwiftName: 'versionUnsupported' + - Name: ZSTD_error_frameParameter_unsupported + SwiftName: 'frameParameterUnsupported' + - Name: ZSTD_error_frameParameter_windowTooLarge + SwiftName: 'frameParameterWindowTooLarge' + - Name: ZSTD_error_corruption_detected + SwiftName: 'corruptionDetected' + - Name: ZSTD_error_checksum_wrong + SwiftName: 'checksumWrong' + - Name: ZSTD_error_literals_headerWrong + SwiftName: 'literalsHeaderWrong' + - Name: ZSTD_error_dictionary_corrupted + SwiftName: 'dictionaryCorrupted' + - Name: ZSTD_error_dictionary_wrong + SwiftName: 'dictionaryWrong' + - Name: ZSTD_error_dictionaryCreation_failed + SwiftName: 'dictionaryCreationFailed' + - Name: ZSTD_error_parameter_unsupported + SwiftName: 'parameterUnsupported' + - Name: ZSTD_error_parameter_combination_unsupported + SwiftName: 'parameterCombinationUnsupported' + - Name: ZSTD_error_parameter_outOfBound + SwiftName: 'parameterOutOfBound' + - Name: ZSTD_error_tableLog_tooLarge + SwiftName: 'tableLogTooLarge' + - Name: ZSTD_error_maxSymbolValue_tooLarge + SwiftName: 'maxSymbolValueTooLarge' + - Name: ZSTD_error_maxSymbolValue_tooSmall + SwiftName: 'maxSymbolValueTooSmall' + - Name: ZSTD_error_cannotProduce_uncompressedBlock + SwiftName: 'cannotProduceUncompressedBlock' + - Name: ZSTD_error_stabilityCondition_notRespected + SwiftName: 'stabilityConditionNotRespected' + - Name: ZSTD_error_stage_wrong + SwiftName: 'stageWrong' + - Name: ZSTD_error_init_missing + SwiftName: 'initMissing' + - Name: ZSTD_error_memory_allocation + SwiftName: 'memoryAllocation' + - Name: ZSTD_error_workSpace_tooSmall + SwiftName: 'workSpaceTooSmall' + - Name: ZSTD_error_dstSize_tooSmall + SwiftName: 'destinationSizeTooSmall' + - Name: ZSTD_error_srcSize_wrong + SwiftName: 'sourceSizeWrong' + - Name: ZSTD_error_dstBuffer_null + SwiftName: 'destinationBufferNull' + - Name: ZSTD_error_noForwardProgress_destFull + SwiftName: 'noForwardProgressDestinationFull' + - Name: ZSTD_error_noForwardProgress_inputEmpty + SwiftName: 'noForwardProgressInputEmpty' + - Name: ZSTD_error_frameIndex_tooLarge + SwiftName: 'frameIndexTooLarge' + - Name: ZSTD_error_seekableIO + SwiftName: 'seekableIO' + - Name: ZSTD_error_dstBuffer_wrong + SwiftName: 'destinationBufferWrong' + - Name: ZSTD_error_srcBuffer_wrong + SwiftName: 'sourceBufferWrong' + - Name: ZSTD_error_sequenceProducer_failed + SwiftName: 'sequenceProducerFailed' + - Name: ZSTD_error_externalSequences_invalid + SwiftName: 'externalSequencesInvalid' + - Name: ZSTD_error_maxCode + SwiftName: 'maxCode' + +# ─── Constants (Globals) ───────────────────────────────────────────────────── +# `#define`d constants in the C headers are imported as Swift `let`s. Rename +# them to camelCase without the ZSTD_ prefix. `defaultCompressionLevelValue` +# is named with a `Value` suffix to avoid colliding with the function +# `defaultCompressionLevel()`. +Globals: + - Name: ZSTD_VERSION_MAJOR + SwiftName: 'versionMajor' + - Name: ZSTD_VERSION_MINOR + SwiftName: 'versionMinor' + - Name: ZSTD_VERSION_RELEASE + SwiftName: 'versionRelease' + - Name: ZSTD_MAGICNUMBER + SwiftName: 'magicNumber' + - Name: ZSTD_MAGIC_DICTIONARY + SwiftName: 'magicDictionary' + - Name: ZSTD_MAGIC_SKIPPABLE_START + SwiftName: 'magicSkippableStart' + - Name: ZSTD_MAGIC_SKIPPABLE_MASK + SwiftName: 'magicSkippableMask' + - Name: ZSTD_BLOCKSIZELOG_MAX + SwiftName: 'blockSizeLog2Max' + - Name: ZSTD_BLOCKSIZE_MAX + SwiftName: 'blockSizeMax' + - Name: ZSTD_CLEVEL_DEFAULT + SwiftName: 'defaultCompressionLevelValue' + - Name: ZSTD_CONTENTSIZE_UNKNOWN + SwiftName: 'contentSizeUnknown' + - Name: ZSTD_CONTENTSIZE_ERROR + SwiftName: 'contentSizeError' + +# ─── Functions ─────────────────────────────────────────────────────────────── +Functions: + - Name: ZSTD_versionNumber + SwiftName: 'versionNumber()' + - Name: ZSTD_versionString + SwiftName: 'versionString()' + - Name: ZSTD_compress + SwiftName: 'compress(into:capacity:from:size:level:)' + - Name: ZSTD_decompress + SwiftName: 'decompress(into:capacity:from:size:)' + - Name: ZSTD_getFrameContentSize + SwiftName: 'frameContentSize(of:size:)' + - Name: ZSTD_getDecompressedSize + SwiftName: 'decompressedSize(of:size:)' + - Name: ZSTD_findFrameCompressedSize + SwiftName: 'frameCompressedSize(of:size:)' + - Name: ZSTD_compressBound + SwiftName: 'compressionBound(_:)' + - Name: ZSTD_isError + SwiftName: 'isError(_:)' + - Name: ZSTD_getErrorCode + SwiftName: 'errorCode(_:)' + - Name: ZSTD_getErrorName + SwiftName: 'errorName(_:)' + - Name: ZSTD_minCLevel + SwiftName: 'minimumCompressionLevel()' + - Name: ZSTD_maxCLevel + SwiftName: 'maximumCompressionLevel()' + - Name: ZSTD_defaultCLevel + SwiftName: 'defaultCompressionLevel()' + - Name: ZSTD_createCCtx + SwiftName: 'createCompressionContext()' + - Name: ZSTD_freeCCtx + SwiftName: 'freeCompressionContext(_:)' + - Name: ZSTD_compressCCtx + SwiftName: 'compress(_:into:capacity:from:size:level:)' + - Name: ZSTD_createDCtx + SwiftName: 'createDecompressionContext()' + - Name: ZSTD_freeDCtx + SwiftName: 'freeDecompressionContext(_:)' + - Name: ZSTD_decompressDCtx + SwiftName: 'decompress(_:into:capacity:from:size:)' + - Name: ZSTD_cParam_getBounds + SwiftName: 'compressionParameterBounds(_:)' + - Name: ZSTD_CCtx_setParameter + SwiftName: 'setCompressionParameter(_:_:to:)' + - Name: ZSTD_CCtx_setPledgedSrcSize + SwiftName: 'setPledgedCompressionSourceSize(_:to:)' + - Name: ZSTD_CCtx_reset + SwiftName: 'resetCompressionContext(_:directive:)' + - Name: ZSTD_compress2 + SwiftName: 'compress(_:into:capacity:from:size:)' + - Name: ZSTD_dParam_getBounds + SwiftName: 'decompressionParameterBounds(_:)' + - Name: ZSTD_DCtx_setParameter + SwiftName: 'setDecompressionParameter(_:_:to:)' + # Bool-typed convenience overloads (wrappers live in Zstd_module.h). + # Same Swift name as the int-typed setters above — Swift dispatches + # to whichever overload matches the literal's type at the call site. + - Name: Zstd_setCompressionFlag + SwiftName: 'setCompressionParameter(_:_:to:)' + - Name: Zstd_setDecompressionFlag + SwiftName: 'setDecompressionParameter(_:_:to:)' + - Name: ZSTD_DCtx_reset + SwiftName: 'resetDecompressionContext(_:directive:)' + - Name: ZSTD_createCStream + SwiftName: 'createCompressionStream()' + - Name: ZSTD_freeCStream + SwiftName: 'freeCompressionStream(_:)' + - Name: ZSTD_compressStream2 + SwiftName: 'compressStream(_:output:input:directive:)' + - Name: ZSTD_CStreamInSize + SwiftName: 'compressionStreamInputSize()' + - Name: ZSTD_CStreamOutSize + SwiftName: 'compressionStreamOutputSize()' + - Name: ZSTD_initCStream + SwiftName: 'initializeCompressionStream(_:level:)' + - Name: ZSTD_compressStream + SwiftName: 'compressStream(_:output:input:)' + - Name: ZSTD_flushStream + SwiftName: 'flushStream(_:output:)' + - Name: ZSTD_endStream + SwiftName: 'endStream(_:output:)' + - Name: ZSTD_createDStream + SwiftName: 'createDecompressionStream()' + - Name: ZSTD_freeDStream + SwiftName: 'freeDecompressionStream(_:)' + - Name: ZSTD_initDStream + SwiftName: 'initializeDecompressionStream(_:)' + - Name: ZSTD_decompressStream + SwiftName: 'decompressStream(_:output:input:)' + - Name: ZSTD_DStreamInSize + SwiftName: 'decompressionStreamInputSize()' + - Name: ZSTD_DStreamOutSize + SwiftName: 'decompressionStreamOutputSize()' + - Name: ZSTD_compress_usingDict + SwiftName: 'compress(_:into:capacity:from:size:dictionary:dictionarySize:level:)' + - Name: ZSTD_decompress_usingDict + SwiftName: 'decompress(_:into:capacity:from:size:dictionary:dictionarySize:)' + - Name: ZSTD_createCDict + SwiftName: 'createCompressionDictionary(from:size:level:)' + - Name: ZSTD_freeCDict + SwiftName: 'freeCompressionDictionary(_:)' + - Name: ZSTD_compress_usingCDict + SwiftName: 'compress(_:into:capacity:from:size:dictionary:)' + - Name: ZSTD_createDDict + SwiftName: 'createDecompressionDictionary(from:size:)' + - Name: ZSTD_freeDDict + SwiftName: 'freeDecompressionDictionary(_:)' + - Name: ZSTD_decompress_usingDDict + SwiftName: 'decompress(_:into:capacity:from:size:dictionary:)' + - Name: ZSTD_getDictID_fromDict + SwiftName: 'dictionaryID(fromDictionary:size:)' + - Name: ZSTD_getDictID_fromCDict + SwiftName: 'dictionaryID(fromCompressionDictionary:)' + - Name: ZSTD_getDictID_fromDDict + SwiftName: 'dictionaryID(fromDecompressionDictionary:)' + - Name: ZSTD_getDictID_fromFrame + SwiftName: 'dictionaryID(fromFrame:size:)' + - Name: ZSTD_CCtx_loadDictionary + SwiftName: 'loadCompressionDictionary(_:from:size:)' + - Name: ZSTD_CCtx_refCDict + SwiftName: 'attachCompressionDictionary(_:_:)' + - Name: ZSTD_CCtx_refPrefix + SwiftName: 'attachCompressionPrefix(_:from:size:)' + - Name: ZSTD_DCtx_loadDictionary + SwiftName: 'loadDecompressionDictionary(_:from:size:)' + - Name: ZSTD_DCtx_refDDict + SwiftName: 'attachDecompressionDictionary(_:_:)' + - Name: ZSTD_DCtx_refPrefix + SwiftName: 'attachDecompressionPrefix(_:from:size:)' + - Name: ZSTD_sizeof_CCtx + SwiftName: 'memoryUsage(ofCompressionContext:)' + - Name: ZSTD_sizeof_DCtx + SwiftName: 'memoryUsage(ofDecompressionContext:)' + - Name: ZSTD_sizeof_CStream + SwiftName: 'memoryUsage(ofCompressionStream:)' + - Name: ZSTD_sizeof_DStream + SwiftName: 'memoryUsage(ofDecompressionStream:)' + - Name: ZSTD_sizeof_CDict + SwiftName: 'memoryUsage(ofCompressionDictionary:)' + - Name: ZSTD_sizeof_DDict + SwiftName: 'memoryUsage(ofDecompressionDictionary:)' + - Name: ZSTD_getErrorString + SwiftName: 'errorString(_:)' + + # ─── ZDICT_* — dictionary builder ───────────────────────────────────────── + # `dictionaryID(fromTrainedDictionary:size:)` is the ZDICT counterpart of + # `dictionaryID(fromDictionary:size:)` (which lives in the main zstd lib); + # both extract a dictID from a raw buffer, but the distinguishing label + # makes the call-site source-of-truth obvious. + - Name: ZDICT_trainFromBuffer + SwiftName: 'trainDictionary(into:capacity:fromSamples:sampleSizes:sampleCount:)' + - Name: ZDICT_finalizeDictionary + SwiftName: 'finalizeDictionary(into:capacity:from:size:samples:sampleSizes:sampleCount:parameters:)' + - Name: ZDICT_getDictID + SwiftName: 'dictionaryID(fromTrainedDictionary:size:)' + - Name: ZDICT_getDictHeaderSize + SwiftName: 'dictionaryHeaderSize(of:size:)' + - Name: ZDICT_isError + SwiftName: 'isDictionaryError(_:)' + - Name: ZDICT_getErrorName + SwiftName: 'dictionaryErrorName(_:)' diff --git a/Sources/Zstd/include/Zstd_module.h b/Sources/Zstd/include/Zstd_module.h new file mode 100644 index 00000000000..b2a8ab4d892 --- /dev/null +++ b/Sources/Zstd/include/Zstd_module.h @@ -0,0 +1,81 @@ +/* + * Umbrella header for the modern `Zstd` Swift module. + * + * Setting ZSTD_FOR_SWIFT_MODERN_API enables the in-header annotations + * that benefit the modernised Swift import (e.g. enum_extensibility on + * each ZSTD_xxx enum). The libzstd alias module uses a different umbrella + * (libzstd_module.h) that omits this macro and thus sees raw enums. + * + * The macro is consumed only by zstd.h / zstd_errors.h / zdict.h, which + * declare the relevant enums. + */ + +/* Defined without a value so the Swift importer doesn't see it as a + * top-level `let ZSTD_FOR_SWIFT_MODERN_API: Int32`. `#if defined(...)` + * still works the same. */ +#ifndef ZSTD_FOR_SWIFT_MODERN_API +# define ZSTD_FOR_SWIFT_MODERN_API +#endif + +#include "zstd.h" +#include "zdict.h" +#include "zstd_errors.h" + +/* Re-export the `ZSTD_*` / `ZDICT_*` `#define`d constants under nicer Swift + * names. apinotes can rename real declarations (functions, typedefs, tags) + * but not preprocessor macros, so we wrap them as real `static const` + * declarations here. The `#undef`s below then hide the originals from + * Swift's macro importer so each constant only shows up once. */ +static const unsigned versionMajor = ZSTD_VERSION_MAJOR; +static const unsigned versionMinor = ZSTD_VERSION_MINOR; +static const unsigned versionRelease = ZSTD_VERSION_RELEASE; +static const unsigned magicNumber = ZSTD_MAGICNUMBER; +static const unsigned magicDictionary = ZSTD_MAGIC_DICTIONARY; +static const unsigned magicSkippableStart = ZSTD_MAGIC_SKIPPABLE_START; +static const unsigned magicSkippableMask = ZSTD_MAGIC_SKIPPABLE_MASK; +static const int blockSizeMax = ZSTD_BLOCKSIZE_MAX; +static const int blockSizeLog2Max = ZSTD_BLOCKSIZELOG_MAX; +static const int defaultCompressionLevelValue = ZSTD_CLEVEL_DEFAULT; +static const unsigned long long contentSizeUnknown = ZSTD_CONTENTSIZE_UNKNOWN; +static const unsigned long long contentSizeError = ZSTD_CONTENTSIZE_ERROR; + +#undef ZSTD_VERSION_MAJOR +#undef ZSTD_VERSION_MINOR +#undef ZSTD_VERSION_RELEASE +#undef ZSTD_MAGICNUMBER +#undef ZSTD_MAGIC_DICTIONARY +#undef ZSTD_MAGIC_SKIPPABLE_START +#undef ZSTD_MAGIC_SKIPPABLE_MASK +#undef ZSTD_BLOCKSIZE_MAX +#undef ZSTD_BLOCKSIZELOG_MAX +#undef ZSTD_CLEVEL_DEFAULT +#undef ZSTD_CONTENTSIZE_UNKNOWN +#undef ZSTD_CONTENTSIZE_ERROR + +/* Bool-typed overloads of the parameter setters so call sites for the + * flag-style parameters (.embedContentSize, .embedChecksum, + * .embedDictionaryID, .enableLongDistanceMatching, etc.) can pass `true` / + * `false` instead of `1` / `0`. Mapped to the same Swift name as the int-typed + * originals via apinotes; Swift's overload resolution picks the bool variant + * for boolean literals and the int variant for numeric literals. + * + * One caveat to be aware of: nothing at compile time stops one from writing + * setCompressionParameter(cctx, .compressionLevel, to: true). zstd would + * receive 1 and silently set the level to 1. The Swift type system can't tell + * which CompressionParameter cases are boolean-valued and which take an + * integer range, because they're all imported as cases of the same C enum. + * The embed* naming (*Flag in the original C) is the only signal at the call + * site. */ +#include + +static inline size_t Zstd_setCompressionFlag(ZSTD_CCtx* cctx, + ZSTD_cParameter param, + bool value) { + return ZSTD_CCtx_setParameter(cctx, param, value ? 1 : 0); +} + +static inline size_t Zstd_setDecompressionFlag(ZSTD_DCtx* dctx, + ZSTD_dParameter param, + bool value) { + return ZSTD_DCtx_setParameter(dctx, param, value ? 1 : 0); +} diff --git a/Sources/Zstd/include/module.modulemap b/Sources/Zstd/include/module.modulemap new file mode 100644 index 00000000000..ab44a1501d8 --- /dev/null +++ b/Sources/Zstd/include/module.modulemap @@ -0,0 +1,11 @@ +// Modulemap for the modern Zstd Swift module. +// Includes textual references to the real headers (which live in lib/), +// then the umbrella header sets the ZSTD_FOR_SWIFT_MODERN_API macro +// and `#include`s those headers, picking up the modern annotations. +module Zstd [extern_c] { + header "Zstd_module.h" + textual header "../../../lib/zstd.h" + textual header "../../../lib/zdict.h" + textual header "../../../lib/zstd_errors.h" + export * +} diff --git a/Sources/libzstd/_empty.c b/Sources/libzstd/_empty.c new file mode 100644 index 00000000000..8a4dfb92063 --- /dev/null +++ b/Sources/libzstd/_empty.c @@ -0,0 +1 @@ +// SwiftPM requires at least one source file per C target. diff --git a/Sources/libzstd/include/libzstd_module.h b/Sources/libzstd/include/libzstd_module.h new file mode 100644 index 00000000000..1bd1c08fd67 --- /dev/null +++ b/Sources/libzstd/include/libzstd_module.h @@ -0,0 +1,10 @@ +/* + * Umbrella header for the legacy `libzstd` Swift module. + * + * Deliberately does NOT define ZSTD_FOR_SWIFT_MODERN_API so the enums in + * zstd.h import as raw C-style values, preserving the historical Swift + * surface that the libzstd module had before the modernisation. + */ +#include "zstd.h" +#include "zdict.h" +#include "zstd_errors.h" diff --git a/Sources/libzstd/include/module.modulemap b/Sources/libzstd/include/module.modulemap new file mode 100644 index 00000000000..e345cfbb6c5 --- /dev/null +++ b/Sources/libzstd/include/module.modulemap @@ -0,0 +1,10 @@ +// Modulemap for the legacy libzstd Swift module. +// Same headers, but the umbrella deliberately omits ZSTD_FOR_SWIFT_MODERN_API, +// so the enums and other annotations stay in their plain-C form. +module libzstd [extern_c] { + header "libzstd_module.h" + textual header "../../../lib/zstd.h" + textual header "../../../lib/zdict.h" + textual header "../../../lib/zstd_errors.h" + export * +} diff --git a/Sources/zstd-example/ZstdExample.swift b/Sources/zstd-example/ZstdExample.swift new file mode 100644 index 00000000000..ab4af3d317b --- /dev/null +++ b/Sources/zstd-example/ZstdExample.swift @@ -0,0 +1,455 @@ +//===----------------------------------------------------------------------===// +// +// zstd-example +// +// An example use of the Zstd Swift module. +// +// Note: the package ships *two* modules built from the same C headers: +// +// • `import libzstd` — the original raw C API (`ZSTD_createCCtx`, +// `ZSTD_compress`, `ZSTD_CCtx_setParameter`, etc.). Kept for source- +// level backwards compatibility with existing Swift code. +// +// • `import Zstd` — the modern, idiomatic API. It cleans up the names +// so that they are more idiomatic in Swift. e.g. the `ZSTD_` prefix is +// stripped and the `CCtx` / `DCtx` / `CDict` / `DDict` / `Ctx` / +// `Dict` / `Param` / `cLevel` / `Src` abbreviations are expanded +// into full words. +// +// So instead of `ZSTD_createCCtx()` you write +// `createCompressionContext()`; instead of `ZSTD_CCtx_setParameter(...)` +// you write `setCompressionParameter(...)`; `ZSTD_compressBound(...)` +// becomes `compressionBound(...)`; and so on. +// +// Related functions are overloaded by Swift argument labels — +// `compress(…)` covers the simple call, +// `compress(_:into:capacity:from:size:)` is the sticky-parameter form, +// `compress(_:into:capacity:from:size:dictionary:)` uses a precomputed +// dictionary, and so on. Same for `decompress(...)`, +// `compressStream(...)`, `dictionaryID(...)`. +// +// Both modules resolve to the same underlying C library; you can pick +// whichever name you prefer, or even import both. The modern names come +// from an `apinotes` file (`lib/Zstd.apinotes`) that the Swift importer +// applies only to the `Zstd` module. +// +// The renames are purely at compile-time (there are no shims or wrappers +// involved), so the compiled code is identical. +// +// Note: unfortunately, the opaque-context types (CCtx, DCtx, CDict, DDict, +// threadPool, CCtx_params) cannot be imported as Swift classes — their +// structs are forward-declared in the public header, while Swift's +// foreign-reference machinery requires a complete definition. They appear in +// Swift as `OpaquePointer`, and you call free functions on them rather than +// methods. Use `defer { freeCompressionContext(context) }` to ensure +// cleanup. +// +//===----------------------------------------------------------------------===// + +import Foundation +import Zstd + +// MARK: - Sendable pointer wrapper + +// OpaquePointer isn't Sendable in Swift 6. zstd's CDict / DDict are +// documented as safe to share across threads, so we wrap them in an +// `@unchecked Sendable` struct for capture into concurrent tasks. +struct SendableOpaquePointer: @unchecked Sendable { + let pointer: OpaquePointer +} + +// MARK: - Error helpers + +struct ZstdError: Error, CustomStringConvertible { + let what: String + let code: Int + + var description: String { + "\(what) failed: \(String(cString: errorName(code)))" + } +} + +@discardableResult +func check(_ result: Int, _ what: String = #function) throws -> Int { + if isError(result) != 0 { + throw ZstdError(what: what, code: result) + } + return result +} + +// MARK: - Entry point + +@main +struct ZstdExample { + static func main() async throws { + print("libzstd \(String(cString: versionString()))") + print("compression levels: \(minimumCompressionLevel())…\(maximumCompressionLevel()) (default \(defaultCompressionLevel()))") + print(String(repeating: "─", count: 60)) + + let payload = makePayload(repetitions: 2_000) + print("source payload: \(payload.count) bytes\n") + + try oneShotDemo(payload: payload) + try contextDemo(payload: payload) + try streamingDemo(payload: payload) + try await dictionaryDemo() + } + + static func makePayload(repetitions: Int) -> [UInt8] { + let phrase = Array("The quick brown fox jumps over the lazy dog. ".utf8) + var bytes: [UInt8] = [] + bytes.reserveCapacity(phrase.count * repetitions) + for _ in 0.. String { + let r = Double(original) / Double(max(compressed, 1)) + return ratioFormatter.string(from: r as NSNumber) ?? "\(r)x" + } +} + +// MARK: - Demo 1: one-shot compress / decompress + +extension ZstdExample { + /// Demonstrates the simplest possible API: compress and decompress in a single call to a top-level function. + /// + /// With `swift_name` annotations in the header, the C `ZSTD_compress` / `ZSTD_decompress` functions import as `compress(into:capacity:from:size:level:)` and `decompress(into:capacity:from:size:)` — call them directly, or qualify them as `Zstd.compress(…)` / `Zstd.decompress(…)` when the bare name might be ambiguous. + static func oneShotDemo(payload: [UInt8]) throws { + print("# one-shot (top-level functions)") + + let bound = compressionBound(payload.count) + var compressed = [UInt8](repeating: 0, count: bound) + + let compressedSize = try check( + payload.withUnsafeBufferPointer { source in + compressed.withUnsafeMutableBufferPointer { destination in + // Note that the order of arguments is not idiomatic Swift, unfortunately. Re-ordering arguments is not currently supported by Swift's C import machinery (it would require writing wrapper functions explicitly - this is intentionally left as an exercise for 3rd parties, since it introduces more compile-time and run-time complexity than mere symbol renames - e.g. then performance is a potential concern because the wrappers might impose some overhead if not fully inlined). + compress(into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count, + level: 3) + } + }, + "compress" + ) + compressed.removeLast(bound - compressedSize) + + var decompressed = [UInt8](repeating: 0, count: payload.count) + let decompressedSize = try check( + compressed.withUnsafeBufferPointer { source in + decompressed.withUnsafeMutableBufferPointer { destination in + decompress(into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count) + } + }, + "decompress" + ) + + precondition(decompressedSize == payload.count) + precondition(decompressed == payload) + + print(" \(payload.count) → \(compressedSize) bytes (\(ratio(of: payload.count, to: compressedSize)))\n") + } +} + +// MARK: - Demo 2: explicit-context compress / decompress + +extension ZstdExample { + /// Demonstrates the explicit-context API. ZSTD_CCtx and ZSTD_DCtx are opaque pointers (`OpaquePointer`); free functions operate on them. The annotated names drop the `ZSTD_` prefix. + static func contextDemo(payload: [UInt8]) throws { + print("# explicit context (CCtx / DCtx)") + + let compressionContext = createCompressionContext() + defer { _ = freeCompressionContext(compressionContext) } + + // Sticky parameters: persist for every subsequent compression on this context, so a server can configure once and reuse. + try check(setCompressionParameter(compressionContext, .compressionLevel, to: 9)) + try check(setCompressionParameter(compressionContext, .embedChecksum, to: true)) + + let bound = compressionBound(payload.count) + var compressed = [UInt8](repeating: 0, count: bound) + + let compressedSize = try check( + payload.withUnsafeBufferPointer { source in + compressed.withUnsafeMutableBufferPointer { destination in + compress(compressionContext, + into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count) + } + } + ) + compressed.removeLast(bound - compressedSize) + + let decompressionContext = createDecompressionContext() + defer { _ = freeDecompressionContext(decompressionContext) } + + var decompressed = [UInt8](repeating: 0, count: payload.count) + let decompressedSize = try check( + compressed.withUnsafeBufferPointer { source in + decompressed.withUnsafeMutableBufferPointer { destination in + decompress(decompressionContext, + into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count) + } + } + ) + + precondition(decompressedSize == payload.count) + precondition(decompressed == payload) + + print(" level 9 + checksum: \(payload.count) → \(compressedSize) bytes (\(ratio(of: payload.count, to: compressedSize)))") + print(" CCtx footprint: \(memoryUsage(ofCompressionContext: compressionContext)) bytes") + print(" DCtx footprint: \(memoryUsage(ofDecompressionContext: decompressionContext)) bytes\n") + } +} + +// MARK: - Demo 3: streaming compress / decompress + +extension ZstdExample { + /// Streams the payload through the compressor in 4 kiB chunks, then streams the compressed bytes back through the decompressor. + static func streamingDemo(payload: [UInt8]) throws { + print("# streaming (chunked compress + decompress)") + + let chunkSize = 4_096 + + let compressionContext = createCompressionContext() + defer { _ = freeCompressionContext(compressionContext) } + + try check(setCompressionParameter(compressionContext, .compressionLevel, to: 5)) + + let compressed = try streamCompress(payload, using: compressionContext!, chunkSize: chunkSize) + + let decompressionContext = createDecompressionContext() + defer { _ = freeDecompressionContext(decompressionContext) } + + let decompressed = try streamDecompress(compressed, using: decompressionContext!, chunkSize: chunkSize) + + precondition(decompressed == payload) + + let chunkCount = (payload.count + chunkSize - 1) / chunkSize + print(" \(payload.count) → \(compressed.count) bytes streamed in \(chunkCount) chunks (\(ratio(of: payload.count, to: compressed.count)))\n") + } + + /// Drives ZSTD_compressStream2 manually, demonstrating the `compressStream(_:output:input:endOp:)` function and the `InputBuffer` / `OutputBuffer` value types. + /// (`compressStream` is overloaded — the 3-argument variant maps to ZSTD_compressStream and the 4-argument variant maps to ZSTD_compressStream2.) + static func streamCompress(_ input: [UInt8], + using context: OpaquePointer, + chunkSize: Int) throws -> [UInt8] { + let outBufferSize = compressionStreamOutputSize() + var outBuffer = [UInt8](repeating: 0, count: outBufferSize) + var compressed = [UInt8]() + + var sourcePosition = 0 + + while true { + let chunkEnd = min(sourcePosition + chunkSize, input.count) + let isLast = (chunkEnd == input.count) + let endOp: EndDirective = isLast ? .end : .continue + + let done: Bool = try input.withUnsafeBufferPointer { source in + try outBuffer.withUnsafeMutableBufferPointer { destination -> Bool in + var inBuf = InputBuffer( + source: UnsafeRawPointer(source.baseAddress!.advanced(by: sourcePosition)), + size: chunkEnd - sourcePosition, + position: 0) + + while true { + var outBuf = OutputBuffer(destination: UnsafeMutableRawPointer(destination.baseAddress!), + size: destination.count, + position: 0) + + let remaining = try check( + compressStream(context, + output: &outBuf, + input: &inBuf, + directive: endOp) + ) + + compressed.append(contentsOf: + UnsafeBufferPointer(start: destination.baseAddress, count: outBuf.position)) + + if isLast { + if remaining == 0 { + return true + } + } else { + if inBuf.position == inBuf.size { + return false + } + } + } + } + } + + if done { + break + } + + sourcePosition = chunkEnd + } + + return compressed + } + + /// Drives ZSTD_decompressStream manually. + static func streamDecompress(_ input: [UInt8], + using decompressionContext: OpaquePointer, + chunkSize: Int) throws -> [UInt8] { + let outBufferSize = decompressionStreamOutputSize() + var outBuffer = [UInt8](repeating: 0, count: outBufferSize) + var decompressed = [UInt8]() + + var sourcePosition = 0 + + while sourcePosition < input.count { + let chunkEnd = min(sourcePosition + chunkSize, input.count) + + try input.withUnsafeBufferPointer { source in + try outBuffer.withUnsafeMutableBufferPointer { destination in + var inBuf = InputBuffer(source: UnsafeRawPointer(source.baseAddress!.advanced(by: sourcePosition)), + size: chunkEnd - sourcePosition, + position: 0) + + while inBuf.position < inBuf.size { + var outBuf = OutputBuffer(destination: UnsafeMutableRawPointer(destination.baseAddress!), + size: destination.count, + position: 0) + + try check( + decompressStream(decompressionContext, output: &outBuf, input: &inBuf) + ) + + decompressed.append(contentsOf: UnsafeBufferPointer(start: destination.baseAddress, count: outBuf.position)) + } + } + } + + sourcePosition = chunkEnd + } + + return decompressed + } +} + +// MARK: - Demo 4: dictionary compression and Sendable CDict + +extension ZstdExample { + /// Builds a "raw content" dictionary from a phrase, then uses it to compress and decompress short messages. Finishes by sharing the same dictionary across four concurrent tasks — possible because the `CDict`'s underlying type is annotated `@unchecked Sendable`. + static func dictionaryDemo() async throws { + print("# dictionary compression with CDict / DDict") + + let dictionaryBytes = Array("The quick brown fox jumps over the lazy dog. ".utf8) + + let compressionDictionary = dictionaryBytes.withUnsafeBufferPointer { bytes in + createCompressionDictionary(from: UnsafeRawPointer(bytes.baseAddress!), + size: bytes.count, + level: 5) + } + defer { _ = freeCompressionDictionary(compressionDictionary) } + + let decompressionDictionary = dictionaryBytes.withUnsafeBufferPointer { bytes in + createDecompressionDictionary(from: UnsafeRawPointer(bytes.baseAddress!), + size: bytes.count) + } + defer { _ = freeDecompressionDictionary(decompressionDictionary) } + + print(" CDict id = \(dictionaryID(fromCompressionDictionary: compressionDictionary)), memory = \(memoryUsage(ofCompressionDictionary: compressionDictionary)) bytes") + print(" DDict id = \(dictionaryID(fromDecompressionDictionary: decompressionDictionary)), memory = \(memoryUsage(ofDecompressionDictionary: decompressionDictionary)) bytes") + + let compressionContext = createCompressionContext() + defer { _ = freeCompressionContext(compressionContext) } + + let decompressionContext = createDecompressionContext() + defer { _ = freeDecompressionContext(decompressionContext) } + + let sampleInput = Array("The quick brown fox jumps over the lazy dog.".utf8) + let bound = compressionBound(sampleInput.count) + var compressed = [UInt8](repeating: 0, count: bound) + + let compressedSize = try check( + sampleInput.withUnsafeBufferPointer { source in + compressed.withUnsafeMutableBufferPointer { destination in + compress(compressionContext, + into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count, + dictionary: compressionDictionary) + } + } + ) + compressed.removeLast(bound - compressedSize) + + var decompressed = [UInt8](repeating: 0, count: sampleInput.count) + let decompressedSize = try check( + compressed.withUnsafeBufferPointer { source in + decompressed.withUnsafeMutableBufferPointer { destination in + decompress(decompressionContext, + into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count, + dictionary: decompressionDictionary) + } + } + ) + + precondition(decompressedSize == sampleInput.count) + precondition(decompressed == sampleInput) + + print(" short message: \(sampleInput.count) → \(compressedSize) bytes\n") + + // Sendable dictionary: share across concurrent tasks + print("# concurrent CDict sharing (Sendable)") + + let sharedDictionary = SendableOpaquePointer(pointer: compressionDictionary!) + + await withTaskGroup(of: (Int, Int).self) { group in + for taskID in 0..<4 { + group.addTask { + let compressionContext = createCompressionContext() + defer { _ = freeCompressionContext(compressionContext) } + + let phrase = "Task \(taskID): The quick brown fox jumps over the lazy dog." + let bytes = Array(phrase.utf8) + let bound = compressionBound(bytes.count) + var out = [UInt8](repeating: 0, count: bound) + + let n = bytes.withUnsafeBufferPointer { source in + out.withUnsafeMutableBufferPointer { destination in + compress(compressionContext, + into: destination.baseAddress!, + capacity: destination.count, + from: source.baseAddress!, + size: source.count, + dictionary: sharedDictionary.pointer) + } + } + + return (taskID, n) + } + } + + for await (taskID, size) in group { + print(" task \(taskID) → \(size) bytes") + } + } + } +} diff --git a/Sources/zstd-example/dump-api.sh b/Sources/zstd-example/dump-api.sh new file mode 100755 index 00000000000..2326495da26 --- /dev/null +++ b/Sources/zstd-example/dump-api.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# This is a helper to show what the Swift API looks like. + +xcrun swift-api-digester \ + -dump-sdk \ + -module Zstd \ + -o /tmp/zstd-api.json \ + -I lib \ + -I Sources/Zstd/include + +python3 <<'PY' +import json + +with open('/tmp/zstd-api.json') as f: + d = json.load(f) + +root = d.get('ABIRoot', d) +top = root.get('children', []) + +# Hide the C-builtin typedefs that the importer drags in. +SKIP_TYPEALIAS = {'__NSConstantString', '__builtin_ms_va_list', '__builtin_va_list', + 'ptrdiff_t', 'rsize_t', 'size_t', 'wchar_t'} + +by_kind = {} +for c in top: + k = c.get('kind', '') + if k == 'Import': + continue + if k == 'TypeAlias' and c.get('name') in SKIP_TYPEALIAS: + continue + by_kind.setdefault(k, []).append(c) + +def banner(title): + print('\n' + '=' * 70) + print(title) + print('=' * 70) + +banner(f"TYPES ({len(by_kind.get('TypeDecl', []))})") +for c in sorted(by_kind.get('TypeDecl', []), key=lambda x: x['name']): + print(f"\n {c['name']}") + for m in c.get('children', []): + mk = m.get('kind', '') + # Skip the synthesised .RawValue typealias inside imported enums. + if mk == 'TypeAlias' and m.get('name') == 'RawValue': + continue + if mk in ('Constructor', 'Var', 'Function'): + print(f" .{m.get('printedName', m.get('name', ''))}") + +banner(f"TYPEALIASES ({len(by_kind.get('TypeAlias', []))})") +for c in sorted(by_kind.get('TypeAlias', []), key=lambda x: x['name']): + print(f" {c['name']}") + +banner(f"CONSTANTS ({len(by_kind.get('Var', []))})") +for c in sorted(by_kind.get('Var', []), key=lambda x: x['name']): + print(f" let {c.get('printedName', c['name'])}") + +banner(f"FUNCTIONS ({len(by_kind.get('Function', []))})") +for c in sorted(by_kind.get('Function', []), key=lambda x: x.get('printedName', x['name'])): + print(f" {c.get('printedName', c['name'])}") +PY diff --git a/lib/module.modulemap b/lib/module.modulemap index e66a210d06b..0c3955cae82 100644 --- a/lib/module.modulemap +++ b/lib/module.modulemap @@ -1,4 +1,7 @@ -module libzstd [extern_c] { +// Private modulemap for the _ZstdCore implementation target. +// The actual public Swift modules live in the Zstd and libzstd targets' +// own modulemaps. This module is not intended for client use. +module _ZstdCore [extern_c] { header "zstd.h" export * diff --git a/lib/zstd.h b/lib/zstd.h index 97fef316fd6..d174a515773 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -15,6 +15,33 @@ /* ====== Dependencies ======*/ #include /* size_t */ +/* ZSTD_ENUM_{OPEN,CLOSED} : optionally tag enums with + * `__attribute__((enum_extensibility(...)))`. Only the modern `Zstd` Swift + * module enables this (via its umbrella header `Zstd_module.h`); the legacy + * `libzstd` module and plain C consumers see no-ops, so the enums import / + * compile exactly as they did pre-annotation. The macros are gated on + * `__has_attribute(enum_extensibility)` for safety on older compilers. + * + * Defined here (before `zstd_errors.h` is included) so that header can use + * the same macros when declaring `ZSTD_ErrorCode`. */ +#if defined(ZSTD_FOR_SWIFT_MODERN_API) && defined(__has_attribute) && __has_attribute(enum_extensibility) +# define ZSTD_ENUM_OPEN __attribute__((enum_extensibility(open))) +# define ZSTD_ENUM_CLOSED __attribute__((enum_extensibility(closed))) +#else +# define ZSTD_ENUM_OPEN +# define ZSTD_ENUM_CLOSED +#endif + +/* Struct field renames for the modern Swift import. apinotes doesn't + * support a `Fields:` section, so we use `swift_name` directly on the + * field declarations — gated on the modern-API macro so the libzstd + * module sees the original C names unchanged. */ +#if defined(ZSTD_FOR_SWIFT_MODERN_API) && defined(__has_attribute) && __has_attribute(swift_name) +# define ZSTD_SWIFT_FIELD(name) __attribute__((swift_name(name))) +#else +# define ZSTD_SWIFT_FIELD(name) +#endif + #include "zstd_errors.h" /* list of errors */ #if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) #include /* INT_MAX */ @@ -332,21 +359,23 @@ ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, */ -/* Compression strategies, listed from fastest to strongest */ -typedef enum { ZSTD_fast=1, - ZSTD_dfast=2, - ZSTD_greedy=3, - ZSTD_lazy=4, - ZSTD_lazy2=5, - ZSTD_btlazy2=6, - ZSTD_btopt=7, - ZSTD_btultra=8, - ZSTD_btultra2=9 +/* Compression strategies, listed from fastest to strongest. + * Marked open: zstd reserves the right to add new strategies in the future. */ +typedef enum ZSTD_ENUM_OPEN { + ZSTD_fast=1, + ZSTD_dfast=2, + ZSTD_greedy=3, + ZSTD_lazy=4, + ZSTD_lazy2=5, + ZSTD_btlazy2=6, + ZSTD_btopt=7, + ZSTD_btultra=8, + ZSTD_btultra2=9 /* note : new strategies _might_ be added in the future. Only the order (from fast to strong) is guaranteed */ } ZSTD_strategy; -typedef enum { +typedef enum ZSTD_ENUM_OPEN { /* compression parameters * Note: When compressing with a ZSTD_CDict these parameters are superseded @@ -586,7 +615,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param */ ZSTDLIB_API size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize); -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_reset_session_only = 1, ZSTD_reset_parameters = 2, ZSTD_reset_session_and_parameters = 3 @@ -637,7 +666,7 @@ ZSTDLIB_API size_t ZSTD_compress2( ZSTD_CCtx* cctx, * Therefore, no new decompression function is necessary. */ -typedef enum { +typedef enum ZSTD_ENUM_OPEN { ZSTD_d_windowLogMax=100, /* Select a size limit (in power of 2) beyond which * the streaming API will refuse to allocate memory buffer @@ -699,15 +728,15 @@ ZSTDLIB_API size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset); ****************************/ typedef struct ZSTD_inBuffer_s { - const void* src; /**< start of input buffer */ - size_t size; /**< size of input buffer */ - size_t pos; /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ + const void* src ZSTD_SWIFT_FIELD("source"); /**< start of input buffer */ + size_t size; /**< size of input buffer */ + size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_inBuffer; typedef struct ZSTD_outBuffer_s { - void* dst; /**< start of output buffer */ - size_t size; /**< size of output buffer */ - size_t pos; /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ + void* dst ZSTD_SWIFT_FIELD("destination"); /**< start of output buffer */ + size_t size; /**< size of output buffer */ + size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_outBuffer; @@ -780,7 +809,7 @@ ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream(void); ZSTDLIB_API size_t ZSTD_freeCStream(ZSTD_CStream* zcs); /* accept NULL pointer */ /*===== Streaming compression functions =====*/ -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_e_continue=0, /* collect more data, encoder decides when to output compressed result, for optimal compression ratio */ ZSTD_e_flush=1, /* flush any data provided so far, * it creates (at least) one new block, that can be decoded immediately on reception; @@ -1370,37 +1399,37 @@ typedef struct { ZSTD_frameParameters fParams; } ZSTD_parameters; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_dct_auto = 0, /* dictionary is "full" when starting with ZSTD_MAGIC_DICTIONARY, otherwise it is "rawContent" */ ZSTD_dct_rawContent = 1, /* ensures dictionary is always loaded as rawContent, even if it starts with ZSTD_MAGIC_DICTIONARY */ ZSTD_dct_fullDict = 2 /* refuses to load a dictionary if it does not respect Zstandard's specification, starting with ZSTD_MAGIC_DICTIONARY */ } ZSTD_dictContentType_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_dlm_byCopy = 0, /**< Copy dictionary content internally */ ZSTD_dlm_byRef = 1 /**< Reference dictionary content -- the dictionary buffer must outlive its users. */ } ZSTD_dictLoadMethod_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_f_zstd1 = 0, /* zstd frame format, specified in zstd_compression_format.md (default) */ ZSTD_f_zstd1_magicless = 1 /* Variant of zstd frame format, without initial 4-bytes magic number. * Useful to save 4 bytes per generated frame. * Decoder cannot recognise automatically this format, requiring this instruction. */ } ZSTD_format_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { /* Note: this enum controls ZSTD_d_forceIgnoreChecksum */ ZSTD_d_validateChecksum = 0, ZSTD_d_ignoreChecksum = 1 } ZSTD_forceIgnoreChecksum_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { /* Note: this enum controls ZSTD_d_refMultipleDDicts */ ZSTD_rmd_refSingleDDict = 0, ZSTD_rmd_refMultipleDDicts = 1 } ZSTD_refMultipleDDicts_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { /* Note: this enum and the behavior it controls are effectively internal * implementation details of the compressor. They are expected to continue * to evolve and should be considered only in the context of extremely @@ -1439,7 +1468,7 @@ typedef enum { ZSTD_dictForceLoad = 3 /* Always reload the dictionary */ } ZSTD_dictAttachPref_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_lcm_auto = 0, /**< Automatically determine the compression mode based on the compression level. * Negative compression levels will be uncompressed, and positive compression * levels will be compressed. */ @@ -1448,7 +1477,7 @@ typedef enum { ZSTD_lcm_uncompressed = 2 /**< Always emit uncompressed literals. */ } ZSTD_literalCompressionMode_e; -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { /* Note: This enum controls features which are conditionally beneficial. * Zstd can take a decision on whether or not to enable the feature (ZSTD_ps_auto), * but setting the switch to ZSTD_ps_enable or ZSTD_ps_disable force enable/disable the feature. @@ -1507,7 +1536,7 @@ ZSTDLIB_STATIC_API unsigned long long ZSTD_decompressBound(const void* src, size * or an error code (if srcSize is too small) */ ZSTDLIB_STATIC_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize); -typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_FrameType_e; +typedef enum ZSTD_ENUM_CLOSED { ZSTD_frame, ZSTD_skippableFrame } ZSTD_FrameType_e; #define ZSTD_frameType_e ZSTD_FrameType_e /* old name */ typedef struct { unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */ @@ -1578,7 +1607,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_decompressionMargin(const void* src, size_t srcSi (blockSize) /* One block of margin */ \ )) -typedef enum { +typedef enum ZSTD_ENUM_CLOSED { ZSTD_sf_noBlockDelimiters = 0, /* ZSTD_Sequence[] has no block delimiters, just sequences */ ZSTD_sf_explicitBlockDelimiters = 1 /* ZSTD_Sequence[] contains explicit block delimiters */ } ZSTD_SequenceFormat_e; @@ -3132,7 +3161,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, si /* misc */ ZSTD_DEPRECATED("This function will likely be removed in the next minor release. It is misleading and has very limited utility.") ZSTDLIB_STATIC_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx); -typedef enum { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e; +typedef enum ZSTD_ENUM_CLOSED { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e; ZSTDLIB_STATIC_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx); diff --git a/lib/zstd_errors.h b/lib/zstd_errors.h index 8ebc95cbb2a..69d5c922caa 100644 --- a/lib/zstd_errors.h +++ b/lib/zstd_errors.h @@ -57,7 +57,7 @@ extern "C" { * This is the only supported way to use the error list < v1.3.1 * note 3 : ZSTD_isError() is always correct, whatever the library version. **********************************************/ -typedef enum { +typedef enum ZSTD_ENUM_OPEN { ZSTD_error_no_error = 0, ZSTD_error_GENERIC = 1, ZSTD_error_prefix_unknown = 10, From a8db689ad1ba2c911511527924f848953e5ef7da Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Tue, 12 May 2026 10:55:13 -0700 Subject: [PATCH 04/11] Improved `dump-api.sh` to include type information as well. --- Sources/zstd-example/dump-api.sh | 119 ++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 16 deletions(-) diff --git a/Sources/zstd-example/dump-api.sh b/Sources/zstd-example/dump-api.sh index 2326495da26..de16034fd03 100755 --- a/Sources/zstd-example/dump-api.sh +++ b/Sources/zstd-example/dump-api.sh @@ -1,6 +1,7 @@ #!/bin/bash -# This is a helper to show what the Swift API looks like. +# This is a helper to show what the Swift API looks like — full signatures +# including parameter and return types, optionality, etc. xcrun swift-api-digester \ -dump-sdk \ @@ -22,6 +23,85 @@ top = root.get('children', []) SKIP_TYPEALIAS = {'__NSConstantString', '__builtin_ms_va_list', '__builtin_va_list', 'ptrdiff_t', 'rsize_t', 'size_t', 'wchar_t'} +# Swift keywords that need to be backticked when used as identifiers. +SWIFT_KEYWORDS = { + 'continue', 'break', 'case', 'class', 'default', 'else', 'for', 'if', + 'in', 'let', 'var', 'while', 'return', 'switch', 'do', 'try', 'throw', + 'init', 'func', 'enum', 'struct', 'protocol', 'extension', 'where', + 'self', 'Self', 'super', 'true', 'false', 'nil', 'fallthrough', + 'guard', 'defer', 'repeat', 'is', 'as', 'throws', 'rethrows', 'async', + 'await', 'inout', 'operator', 'precedencegroup', 'subscript', +} + +def strip_module(t: str) -> str: + """Remove common module prefixes for readability.""" + if not t: + return t + return t.replace('Swift.', '').replace('Zstd.', '') + +def backtick(name: str) -> str: + return f"`{name}`" if name in SWIFT_KEYWORDS else name + +def type_children(node): + """Return only the type-shaped children (parameters / return type).""" + return [c for c in node.get('children', []) + if c.get('kind', '').startswith('Type') + and c.get('kind') != 'TypeAlias'] + +def split_signature(printed: str): + """Split 'foo(a:b:c:)' into ('foo', ['a','b','c']).""" + if '(' not in printed: + return printed, [] + base, _, rest = printed.partition('(') + return base, [l for l in rest.rstrip(')').split(':') if l] + +def emit_function(node, indent: str = '') -> str: + name, labels = split_signature(node.get('printedName', node['name'])) + types = type_children(node) + if types: + ret = strip_module(types[0].get('printedName', '?')) + params = [strip_module(t.get('printedName', '?')) for t in types[1:]] + else: + ret, params = '?', [] + + is_init = node.get('declKind') == 'Constructor' + if is_init: + # Constructors include the Self type as the first "param type" in the + # JSON dump; drop it since it isn't actually written at the call site. + params = params # params already excludes ret; for init, treat all as actual params + pieces = [] + for i, lbl in enumerate(labels): + t = params[i] if i < len(params) else '?' + pieces.append(f"_: {t}" if lbl == '_' else f"{lbl}: {t}") + params_str = ', '.join(pieces) + + if is_init: + return f"{indent}init({params_str})" + ret_part = '' if ret in ('()', 'Void') else f" -> {ret}" + return f"{indent}func {backtick(name)}({params_str}){ret_part}" + +def emit_var(node, indent: str = '', top_level: bool = False) -> str: + name = backtick(node.get('printedName', node['name'])) + decl = node.get('declKind', 'Var') + if decl == 'EnumElement': + return f"{indent}case {name}" + types = type_children(node) + t = strip_module(types[0].get('printedName', '?')) if types else '?' + keyword = 'let' if top_level else 'var' + suffix = '' if top_level else ' { get }' + return f"{indent}{keyword} {name}: {t}{suffix}" + +def emit_typealias(node, indent: str = '') -> str: + name = node.get('printedName', node['name']) + types = type_children(node) + aliased = strip_module(types[0].get('printedName', '?')) if types else '?' + return f"{indent}typealias {name} = {aliased}" + +def banner(title: str): + print('\n' + '=' * 70) + print(title) + print('=' * 70) + by_kind = {} for c in top: k = c.get('kind', '') @@ -31,31 +111,38 @@ for c in top: continue by_kind.setdefault(k, []).append(c) -def banner(title): - print('\n' + '=' * 70) - print(title) - print('=' * 70) - +# ---- Types ----------------------------------------------------------------- banner(f"TYPES ({len(by_kind.get('TypeDecl', []))})") for c in sorted(by_kind.get('TypeDecl', []), key=lambda x: x['name']): print(f"\n {c['name']}") for m in c.get('children', []): mk = m.get('kind', '') - # Skip the synthesised .RawValue typealias inside imported enums. - if mk == 'TypeAlias' and m.get('name') == 'RawValue': - continue - if mk in ('Constructor', 'Var', 'Function'): - print(f" .{m.get('printedName', m.get('name', ''))}") + decl = m.get('declKind', '') + if mk == 'TypeAlias': + # Skip the synthesised .RawValue alias inside imported enums. + if m.get('name') == 'RawValue': + continue + print(emit_typealias(m, indent=' ')) + elif decl == 'Constructor' or mk == 'Constructor': + print(emit_function(m, indent=' ')) + elif mk == 'Function': + print(emit_function(m, indent=' ')) + elif mk == 'Var': + print(emit_var(m, indent=' ')) +# ---- Typealiases (top-level) ---------------------------------------------- banner(f"TYPEALIASES ({len(by_kind.get('TypeAlias', []))})") for c in sorted(by_kind.get('TypeAlias', []), key=lambda x: x['name']): - print(f" {c['name']}") + print(' ' + emit_typealias(c)) +# ---- Constants ------------------------------------------------------------ banner(f"CONSTANTS ({len(by_kind.get('Var', []))})") for c in sorted(by_kind.get('Var', []), key=lambda x: x['name']): - print(f" let {c.get('printedName', c['name'])}") + print(' ' + emit_var(c, top_level=True)) -banner(f"FUNCTIONS ({len(by_kind.get('Function', []))})") -for c in sorted(by_kind.get('Function', []), key=lambda x: x.get('printedName', x['name'])): - print(f" {c.get('printedName', c['name'])}") +# ---- Functions ------------------------------------------------------------ +funcs = by_kind.get('Function', []) +banner(f"FUNCTIONS ({len(funcs)})") +for c in sorted(funcs, key=lambda x: x.get('printedName', x['name'])): + print(' ' + emit_function(c)) PY From b12fdf675224d7ded87bf7f202908590809ef560 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Tue, 12 May 2026 10:58:01 -0700 Subject: [PATCH 05/11] Added nullability annotations. These apply to the C API as well (but are not supported by all compilers, e.g. GCC still doesn't AFAIK). They are important for Swift because they prevent the API using implicitly unwrapped optionals everywhere. In many cases the parameter (or return value) is _not_ in fact optional, and when it is that should be exposed explicitly in Swift (per Swift best practice). --- Sources/Zstd/include/Zstd_module.h | 17 +++- Sources/libzstd/include/libzstd_module.h | 6 ++ Sources/zstd-example/ZstdExample.swift | 61 +++++++++---- lib/zdict.h | 10 +++ lib/zstd.h | 109 +++++++++++------------ lib/zstd_annotations.h | 89 ++++++++++++++++++ lib/zstd_errors.h | 10 +++ 7 files changed, 226 insertions(+), 76 deletions(-) create mode 100644 lib/zstd_annotations.h diff --git a/Sources/Zstd/include/Zstd_module.h b/Sources/Zstd/include/Zstd_module.h index b2a8ab4d892..8c463c0a973 100644 --- a/Sources/Zstd/include/Zstd_module.h +++ b/Sources/Zstd/include/Zstd_module.h @@ -11,12 +11,16 @@ */ /* Defined without a value so the Swift importer doesn't see it as a - * top-level `let ZSTD_FOR_SWIFT_MODERN_API: Int32`. `#if defined(...)` + * top-level `let ZSTD_FOR_SWIFT_MODERN_API: Int32`. `#if defined(...)` * still works the same. */ #ifndef ZSTD_FOR_SWIFT_MODERN_API # define ZSTD_FOR_SWIFT_MODERN_API #endif +/* Nullability annotations and assume_nonnull are applied unconditionally + * inside zstd.h / zdict.h (clang's pragma is file-scoped). The legacy + * libzstd umbrella forces ZSTD_NULLABILITY=0 to suppress them and + * preserve its pre-annotation Swift surface. */ #include "zstd.h" #include "zdict.h" #include "zstd_errors.h" @@ -68,6 +72,8 @@ static const unsigned long long contentSizeError = ZSTD_CONTENTSIZE_E * site. */ #include +#pragma clang assume_nonnull begin + static inline size_t Zstd_setCompressionFlag(ZSTD_CCtx* cctx, ZSTD_cParameter param, bool value) { @@ -79,3 +85,12 @@ static inline size_t Zstd_setDecompressionFlag(ZSTD_DCtx* dctx, bool value) { return ZSTD_DCtx_setParameter(dctx, param, value ? 1 : 0); } + +#pragma clang assume_nonnull end + +/* Hide implementation-detail macros from Swift, which would otherwise + * import them as top-level `let` constants (`ZSTD_NULLABILITY`, the + * `` sentinel). The features they control are already in + * scope; the macro values are not part of the API. */ +#undef ZSTD_NULLABILITY +#undef __bool_true_false_are_defined diff --git a/Sources/libzstd/include/libzstd_module.h b/Sources/libzstd/include/libzstd_module.h index 1bd1c08fd67..dd90652968d 100644 --- a/Sources/libzstd/include/libzstd_module.h +++ b/Sources/libzstd/include/libzstd_module.h @@ -4,7 +4,13 @@ * Deliberately does NOT define ZSTD_FOR_SWIFT_MODERN_API so the enums in * zstd.h import as raw C-style values, preserving the historical Swift * surface that the libzstd module had before the modernisation. + * + * Also forces ZSTD_NULLABILITY to 0 so the `_Nullable` / `_Nonnull` + * annotations and the `assume_nonnull` pragma added later are not + * applied — pointers continue to import as implicitly-unwrapped + * optionals, matching the original libzstd Swift surface exactly. */ +#define ZSTD_NULLABILITY 0 #include "zstd.h" #include "zdict.h" #include "zstd_errors.h" diff --git a/Sources/zstd-example/ZstdExample.swift b/Sources/zstd-example/ZstdExample.swift index ab4af3d317b..ef82bbab5cc 100644 --- a/Sources/zstd-example/ZstdExample.swift +++ b/Sources/zstd-example/ZstdExample.swift @@ -60,19 +60,24 @@ struct SendableOpaquePointer: @unchecked Sendable { // MARK: - Error helpers -struct ZstdError: Error, CustomStringConvertible { - let what: String - let code: Int +enum ZstdError: Error, CustomStringConvertible { + case operation(_ what: String, code: Int) + case allocationFailed(_ what: String) var description: String { - "\(what) failed: \(String(cString: errorName(code)))" + switch self { + case .operation(let what, let code): + return "\(what) failed: \(String(cString: errorName(code)))" + case .allocationFailed(let what): + return "\(what) returned nil — zstd allocation failed" + } } } @discardableResult func check(_ result: Int, _ what: String = #function) throws -> Int { if isError(result) != 0 { - throw ZstdError(what: what, code: result) + throw ZstdError.operation(what, code: result) } return result } @@ -172,7 +177,9 @@ extension ZstdExample { static func contextDemo(payload: [UInt8]) throws { print("# explicit context (CCtx / DCtx)") - let compressionContext = createCompressionContext() + guard let compressionContext = createCompressionContext() else { + throw ZstdError.allocationFailed("createCompressionContext") + } defer { _ = freeCompressionContext(compressionContext) } // Sticky parameters: persist for every subsequent compression on this context, so a server can configure once and reuse. @@ -195,7 +202,9 @@ extension ZstdExample { ) compressed.removeLast(bound - compressedSize) - let decompressionContext = createDecompressionContext() + guard let decompressionContext = createDecompressionContext() else { + throw ZstdError.allocationFailed("createDecompressionContext") + } defer { _ = freeDecompressionContext(decompressionContext) } var decompressed = [UInt8](repeating: 0, count: payload.count) @@ -229,17 +238,21 @@ extension ZstdExample { let chunkSize = 4_096 - let compressionContext = createCompressionContext() + guard let compressionContext = createCompressionContext() else { + throw ZstdError.allocationFailed("createCompressionContext") + } defer { _ = freeCompressionContext(compressionContext) } try check(setCompressionParameter(compressionContext, .compressionLevel, to: 5)) - let compressed = try streamCompress(payload, using: compressionContext!, chunkSize: chunkSize) + let compressed = try streamCompress(payload, using: compressionContext, chunkSize: chunkSize) - let decompressionContext = createDecompressionContext() + guard let decompressionContext = createDecompressionContext() else { + throw ZstdError.allocationFailed("createDecompressionContext") + } defer { _ = freeDecompressionContext(decompressionContext) } - let decompressed = try streamDecompress(compressed, using: decompressionContext!, chunkSize: chunkSize) + let decompressed = try streamDecompress(compressed, using: decompressionContext, chunkSize: chunkSize) precondition(decompressed == payload) @@ -357,26 +370,34 @@ extension ZstdExample { let dictionaryBytes = Array("The quick brown fox jumps over the lazy dog. ".utf8) - let compressionDictionary = dictionaryBytes.withUnsafeBufferPointer { bytes in + guard let compressionDictionary = (dictionaryBytes.withUnsafeBufferPointer { bytes in createCompressionDictionary(from: UnsafeRawPointer(bytes.baseAddress!), size: bytes.count, level: 5) + }) else { + throw ZstdError.allocationFailed("createCompressionDictionary") } defer { _ = freeCompressionDictionary(compressionDictionary) } - let decompressionDictionary = dictionaryBytes.withUnsafeBufferPointer { bytes in + guard let decompressionDictionary = (dictionaryBytes.withUnsafeBufferPointer { bytes in createDecompressionDictionary(from: UnsafeRawPointer(bytes.baseAddress!), size: bytes.count) + }) else { + throw ZstdError.allocationFailed("createDecompressionDictionary") } defer { _ = freeDecompressionDictionary(decompressionDictionary) } print(" CDict id = \(dictionaryID(fromCompressionDictionary: compressionDictionary)), memory = \(memoryUsage(ofCompressionDictionary: compressionDictionary)) bytes") print(" DDict id = \(dictionaryID(fromDecompressionDictionary: decompressionDictionary)), memory = \(memoryUsage(ofDecompressionDictionary: decompressionDictionary)) bytes") - let compressionContext = createCompressionContext() + guard let compressionContext = createCompressionContext() else { + throw ZstdError.allocationFailed("createCompressionContext") + } defer { _ = freeCompressionContext(compressionContext) } - let decompressionContext = createDecompressionContext() + guard let decompressionContext = createDecompressionContext() else { + throw ZstdError.allocationFailed("createDecompressionContext") + } defer { _ = freeDecompressionContext(decompressionContext) } let sampleInput = Array("The quick brown fox jumps over the lazy dog.".utf8) @@ -419,12 +440,14 @@ extension ZstdExample { // Sendable dictionary: share across concurrent tasks print("# concurrent CDict sharing (Sendable)") - let sharedDictionary = SendableOpaquePointer(pointer: compressionDictionary!) + let sharedDictionary = SendableOpaquePointer(pointer: compressionDictionary) - await withTaskGroup(of: (Int, Int).self) { group in + try await withThrowingTaskGroup(of: (Int, Int).self) { group in for taskID in 0..<4 { group.addTask { - let compressionContext = createCompressionContext() + guard let compressionContext = createCompressionContext() else { + throw ZstdError.allocationFailed("createCompressionContext") + } defer { _ = freeCompressionContext(compressionContext) } let phrase = "Task \(taskID): The quick brown fox jumps over the lazy dog." @@ -447,7 +470,7 @@ extension ZstdExample { } } - for await (taskID, size) in group { + for try await (taskID, size) in group { print(" task \(taskID) → \(size) bytes") } } diff --git a/lib/zdict.h b/lib/zdict.h index 599b793013b..54f30d60bc5 100644 --- a/lib/zdict.h +++ b/lib/zdict.h @@ -15,6 +15,12 @@ /*====== Dependencies ======*/ #include /* size_t */ +#include "zstd_annotations.h" + +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull begin +#endif + #if defined (__cplusplus) extern "C" { #endif @@ -275,6 +281,10 @@ ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode); } #endif +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull end +#endif + #endif /* ZSTD_ZDICT_H */ #if defined(ZDICT_STATIC_LINKING_ONLY) && !defined(ZSTD_ZDICT_H_STATIC) diff --git a/lib/zstd.h b/lib/zstd.h index d174a515773..3db654b4b08 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -15,38 +15,24 @@ /* ====== Dependencies ======*/ #include /* size_t */ -/* ZSTD_ENUM_{OPEN,CLOSED} : optionally tag enums with - * `__attribute__((enum_extensibility(...)))`. Only the modern `Zstd` Swift - * module enables this (via its umbrella header `Zstd_module.h`); the legacy - * `libzstd` module and plain C consumers see no-ops, so the enums import / - * compile exactly as they did pre-annotation. The macros are gated on - * `__has_attribute(enum_extensibility)` for safety on older compilers. - * - * Defined here (before `zstd_errors.h` is included) so that header can use - * the same macros when declaring `ZSTD_ErrorCode`. */ -#if defined(ZSTD_FOR_SWIFT_MODERN_API) && defined(__has_attribute) && __has_attribute(enum_extensibility) -# define ZSTD_ENUM_OPEN __attribute__((enum_extensibility(open))) -# define ZSTD_ENUM_CLOSED __attribute__((enum_extensibility(closed))) -#else -# define ZSTD_ENUM_OPEN -# define ZSTD_ENUM_CLOSED -#endif - -/* Struct field renames for the modern Swift import. apinotes doesn't - * support a `Fields:` section, so we use `swift_name` directly on the - * field declarations — gated on the modern-API macro so the libzstd - * module sees the original C names unchanged. */ -#if defined(ZSTD_FOR_SWIFT_MODERN_API) && defined(__has_attribute) && __has_attribute(swift_name) -# define ZSTD_SWIFT_FIELD(name) __attribute__((swift_name(name))) -#else -# define ZSTD_SWIFT_FIELD(name) -#endif +/* All the macros that decorate this header for Swift import / clang + * static analysis are defined in a shared file — see that header for + * details on each macro and the ZSTD_NULLABILITY override knob. */ +#include "zstd_annotations.h" #include "zstd_errors.h" /* list of errors */ #if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) #include /* INT_MAX */ #endif /* ZSTD_STATIC_LINKING_ONLY */ +/* Pointers in the API below are nonnull unless explicitly tagged + * ZSTD_NULLABLE. Gated on ZSTD_NULLABILITY (default-on for clang) so + * the legacy libzstd umbrella can switch back to unspecified + * nullability by `#define ZSTD_NULLABILITY 0` before inclusion. */ +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull begin +#endif + #if defined (__cplusplus) extern "C" { #endif @@ -305,8 +291,8 @@ ZSTDLIB_API int ZSTD_defaultCLevel(void); /*!< default compress * use one different context per thread . */ typedef struct ZSTD_CCtx_s ZSTD_CCtx; -ZSTDLIB_API ZSTD_CCtx* ZSTD_createCCtx(void); -ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx); /* compatible with NULL pointer */ +ZSTDLIB_API ZSTD_CCtx* ZSTD_NULLABLE ZSTD_createCCtx(void); +ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* ZSTD_NULLABLE cctx); /*! ZSTD_compressCCtx() : * Same as ZSTD_compress(), using an explicit ZSTD_CCtx. @@ -328,8 +314,8 @@ ZSTDLIB_API size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, * This will make workload friendlier for system's memory. * Use one context per thread for parallel execution. */ typedef struct ZSTD_DCtx_s ZSTD_DCtx; -ZSTDLIB_API ZSTD_DCtx* ZSTD_createDCtx(void); -ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx); /* accept NULL pointer */ +ZSTDLIB_API ZSTD_DCtx* ZSTD_NULLABLE ZSTD_createDCtx(void); +ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* ZSTD_NULLABLE dctx); /*! ZSTD_decompressDCtx() : * Same as ZSTD_decompress(), @@ -728,15 +714,15 @@ ZSTDLIB_API size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset); ****************************/ typedef struct ZSTD_inBuffer_s { - const void* src ZSTD_SWIFT_FIELD("source"); /**< start of input buffer */ - size_t size; /**< size of input buffer */ - size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ + const void* ZSTD_NULLABLE src ZSTD_SWIFT_FIELD("source"); /**< start of input buffer */ + size_t size; /**< size of input buffer */ + size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_inBuffer; typedef struct ZSTD_outBuffer_s { - void* dst ZSTD_SWIFT_FIELD("destination"); /**< start of output buffer */ - size_t size; /**< size of output buffer */ - size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ + void* ZSTD_NULLABLE dst ZSTD_SWIFT_FIELD("destination"); /**< start of output buffer */ + size_t size; /**< size of output buffer */ + size_t pos ZSTD_SWIFT_FIELD("position"); /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_outBuffer; @@ -805,8 +791,8 @@ typedef struct ZSTD_outBuffer_s { typedef ZSTD_CCtx ZSTD_CStream; /**< CCtx and CStream are now effectively same object (>= v1.3.0) */ /* Continue to distinguish them for compatibility with older versions <= v1.2.0 */ /*===== ZSTD_CStream management functions =====*/ -ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream(void); -ZSTDLIB_API size_t ZSTD_freeCStream(ZSTD_CStream* zcs); /* accept NULL pointer */ +ZSTDLIB_API ZSTD_CStream* ZSTD_NULLABLE ZSTD_createCStream(void); +ZSTDLIB_API size_t ZSTD_freeCStream(ZSTD_CStream* ZSTD_NULLABLE zcs); /*===== Streaming compression functions =====*/ typedef enum ZSTD_ENUM_CLOSED { @@ -937,8 +923,8 @@ ZSTDLIB_API size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output); typedef ZSTD_DCtx ZSTD_DStream; /**< DCtx and DStream are now effectively same object (>= v1.3.0) */ /* For compatibility with versions <= v1.2.0, prefer differentiating them. */ /*===== ZSTD_DStream management functions =====*/ -ZSTDLIB_API ZSTD_DStream* ZSTD_createDStream(void); -ZSTDLIB_API size_t ZSTD_freeDStream(ZSTD_DStream* zds); /* accept NULL pointer */ +ZSTDLIB_API ZSTD_DStream* ZSTD_NULLABLE ZSTD_createDStream(void); +ZSTDLIB_API size_t ZSTD_freeDStream(ZSTD_DStream* ZSTD_NULLABLE zds); /*===== Streaming decompression functions =====*/ @@ -993,7 +979,7 @@ ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, - const void* dict,size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, int compressionLevel); /*! ZSTD_decompress_usingDict() : @@ -1005,7 +991,7 @@ ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx, ZSTDLIB_API size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, - const void* dict,size_t dictSize); + const void* ZSTD_NULLABLE dict, size_t dictSize); /*********************************** @@ -1025,13 +1011,14 @@ typedef struct ZSTD_CDict_s ZSTD_CDict; * in which case the only thing that it transports is the @compressionLevel. * This can be useful in a pipeline featuring ZSTD_compress_usingCDict() exclusively, * expecting a ZSTD_CDict parameter with any data, including those without a known dictionary. */ -ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict(const void* dictBuffer, size_t dictSize, - int compressionLevel); +ZSTDLIB_API ZSTD_CDict* ZSTD_NULLABLE ZSTD_createCDict(const void* ZSTD_NULLABLE dictBuffer, + size_t dictSize, + int compressionLevel); /*! ZSTD_freeCDict() : * Function frees memory allocated by ZSTD_createCDict(). * If a NULL pointer is passed, no operation is performed. */ -ZSTDLIB_API size_t ZSTD_freeCDict(ZSTD_CDict* CDict); +ZSTDLIB_API size_t ZSTD_freeCDict(ZSTD_CDict* ZSTD_NULLABLE CDict); /*! ZSTD_compress_usingCDict() : * Compression using a digested Dictionary. @@ -1049,12 +1036,13 @@ typedef struct ZSTD_DDict_s ZSTD_DDict; /*! ZSTD_createDDict() : * Create a digested dictionary, ready to start decompression operation without startup delay. * dictBuffer can be released after DDict creation, as its content is copied inside DDict. */ -ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict(const void* dictBuffer, size_t dictSize); +ZSTDLIB_API ZSTD_DDict* ZSTD_NULLABLE ZSTD_createDDict(const void* ZSTD_NULLABLE dictBuffer, + size_t dictSize); /*! ZSTD_freeDDict() : * Function frees memory allocated with ZSTD_createDDict() * If a NULL pointer is passed, no operation is performed. */ -ZSTDLIB_API size_t ZSTD_freeDDict(ZSTD_DDict* ddict); +ZSTDLIB_API size_t ZSTD_freeDDict(ZSTD_DDict* ZSTD_NULLABLE ddict); /*! ZSTD_decompress_usingDDict() : * Decompression using a digested Dictionary. @@ -1073,19 +1061,19 @@ ZSTDLIB_API size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx, * Provides the dictID stored within dictionary. * if @return == 0, the dictionary is not conformant with Zstandard specification. * It can still be loaded, but as a content-only dictionary. */ -ZSTDLIB_API unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize); +ZSTDLIB_API unsigned ZSTD_getDictID_fromDict(const void* ZSTD_NULLABLE dict, size_t dictSize); /*! ZSTD_getDictID_fromCDict() : Requires v1.5.0+ * Provides the dictID of the dictionary loaded into `cdict`. * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty. * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */ -ZSTDLIB_API unsigned ZSTD_getDictID_fromCDict(const ZSTD_CDict* cdict); +ZSTDLIB_API unsigned ZSTD_getDictID_fromCDict(const ZSTD_CDict* ZSTD_NULLABLE cdict); /*! ZSTD_getDictID_fromDDict() : Requires v1.4.0+ * Provides the dictID of the dictionary loaded into `ddict`. * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty. * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */ -ZSTDLIB_API unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict); +ZSTDLIB_API unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ZSTD_NULLABLE ddict); /*! ZSTD_getDictID_fromFrame() : Requires v1.4.0+ * Provides the dictID required to decompressed the frame stored within `src`. @@ -1134,7 +1122,7 @@ ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize); * If you want to employ LDM on some large dictionary content, * prefer employing ZSTD_CCtx_refPrefix() described below. */ -ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); +ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE dict, size_t dictSize); /*! ZSTD_CCtx_refCDict() : Requires v1.4.0+ * Reference a prepared dictionary, to be used for all future compressed frames. @@ -1148,7 +1136,7 @@ ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, s * Note 1 : Currently, only one dictionary can be managed. * Referencing a new dictionary effectively "discards" any previous one. * Note 2 : CDict is just referenced, its lifetime must outlive its usage within CCtx. */ -ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); +ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* ZSTD_NULLABLE cdict); /*! ZSTD_CCtx_refPrefix() : Requires v1.4.0+ * Reference a prefix (single-usage dictionary) for next compressed frame. @@ -1170,7 +1158,8 @@ ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); * Note 4 : By default, the prefix is interpreted as raw content (ZSTD_dct_rawContent). * Use experimental ZSTD_CCtx_refPrefix_advanced() to alter dictionary interpretation. */ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix(ZSTD_CCtx* cctx, - const void* prefix, size_t prefixSize); + const void* ZSTD_NULLABLE prefix, + size_t prefixSize); /*! ZSTD_DCtx_loadDictionary() : Requires v1.4.0+ * Create an internal DDict from dict buffer, to be used to decompress all future frames. @@ -1187,7 +1176,9 @@ ZSTDLIB_API size_t ZSTD_CCtx_refPrefix(ZSTD_CCtx* cctx, * Note 3 : Use ZSTD_DCtx_loadDictionary_advanced() to take control of * how dictionary content is loaded and interpreted. */ -ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); +ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, + const void* ZSTD_NULLABLE dict, + size_t dictSize); /*! ZSTD_DCtx_refDDict() : Requires v1.4.0+ * Reference a prepared dictionary, to be used to decompress next frames. @@ -1206,7 +1197,8 @@ ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, s * Special: referencing a NULL DDict means "return to no-dictionary mode". * Note 2 : DDict is just referenced, its lifetime must outlive its usage from DCtx. */ -ZSTDLIB_API size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); +ZSTDLIB_API size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, + const ZSTD_DDict* ZSTD_NULLABLE ddict); /*! ZSTD_DCtx_refPrefix() : Requires v1.4.0+ * Reference a prefix (single-usage dictionary) to decompress next frame. @@ -1225,7 +1217,8 @@ ZSTDLIB_API size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict); * A full dictionary is more costly, as it requires building tables. */ ZSTDLIB_API size_t ZSTD_DCtx_refPrefix(ZSTD_DCtx* dctx, - const void* prefix, size_t prefixSize); + const void* ZSTD_NULLABLE prefix, + size_t prefixSize); /* === Memory management === */ @@ -1243,6 +1236,10 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); } #endif +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull end +#endif + #endif /* ZSTD_H_235446 */ diff --git a/lib/zstd_annotations.h b/lib/zstd_annotations.h new file mode 100644 index 00000000000..40a58bb5ac8 --- /dev/null +++ b/lib/zstd_annotations.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +/* + * Shared annotation infrastructure for zstd's public headers. + * + * Defines feature-check polyfills and the macros that decorate the public + * declarations with information used by Swift, clang's static analyser, and + * other tooling: + * + * ZSTD_NULLABILITY Switch (1/0). Default on for clang. Including + * code can `#define ZSTD_NULLABILITY 0` BEFORE the + * first inclusion of a zstd header to suppress all + * the nullability output below — this is what the + * legacy `libzstd` Swift umbrella does to preserve + * its pre-annotation surface. + * + * ZSTD_NULLABLE Expands to `_Nullable` when ZSTD_NULLABILITY is + * on, empty otherwise. Used in declarations to mark + * the specific pointers that genuinely accept NULL. + * The headers' API regions are wrapped in + * `#pragma clang assume_nonnull` so pointers + * without this marker default to non-null. + * + * ZSTD_ENUM_OPEN + * ZSTD_ENUM_CLOSED `__attribute__((enum_extensibility(...)))` for + * the modern Swift import — apply directly to the + * `typedef enum` tag. Gated on + * ZSTD_FOR_SWIFT_MODERN_API so the legacy libzstd + * module imports plain Int32 raw values. + * + * ZSTD_SWIFT_FIELD(n) `__attribute__((swift_name(n)))` for renaming a + * struct field as it appears in Swift. apinotes + * can rename functions / typedefs / tags but not + * fields, so this lives in the headers themselves. + * Gated on ZSTD_FOR_SWIFT_MODERN_API. + */ +#ifndef ZSTD_ANNOTATIONS_H +#define ZSTD_ANNOTATIONS_H + +/* `__has_feature` and `__has_attribute` are clang extensions. The C + * preprocessor doesn't short-circuit `defined(x) && x(...)` at parse + * time — unknown identifiers become `0`, so e.g. + * `__has_feature(nullability)` would become `0(nullability)` and fail + * to parse on GCC. The polyfills below make the macros safely expand + * to 0 on toolchains that don't define them. */ +#ifndef __has_feature +# define __has_feature(x) 0 +#endif +#ifndef __has_attribute +# define __has_attribute(x) 0 +#endif + +#ifndef ZSTD_NULLABILITY +# if __has_feature(nullability) +# define ZSTD_NULLABILITY 1 +# else +# define ZSTD_NULLABILITY 0 +# endif +#endif + +#if ZSTD_NULLABILITY +# define ZSTD_NULLABLE _Nullable +#else +# define ZSTD_NULLABLE +#endif + +#if defined(ZSTD_FOR_SWIFT_MODERN_API) && __has_attribute(enum_extensibility) +# define ZSTD_ENUM_OPEN __attribute__((enum_extensibility(open))) +# define ZSTD_ENUM_CLOSED __attribute__((enum_extensibility(closed))) +#else +# define ZSTD_ENUM_OPEN +# define ZSTD_ENUM_CLOSED +#endif + +#if defined(ZSTD_FOR_SWIFT_MODERN_API) && __has_attribute(swift_name) +# define ZSTD_SWIFT_FIELD(name) __attribute__((swift_name(name))) +#else +# define ZSTD_SWIFT_FIELD(name) +#endif + +#endif /* ZSTD_ANNOTATIONS_H */ diff --git a/lib/zstd_errors.h b/lib/zstd_errors.h index 69d5c922caa..3123c396105 100644 --- a/lib/zstd_errors.h +++ b/lib/zstd_errors.h @@ -11,6 +11,12 @@ #ifndef ZSTD_ERRORS_H_398273423 #define ZSTD_ERRORS_H_398273423 +#include "zstd_annotations.h" + +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull begin +#endif + #if defined (__cplusplus) extern "C" { #endif @@ -104,4 +110,8 @@ ZSTDERRORLIB_API const char* ZSTD_getErrorString(ZSTD_ErrorCode code); /**< Sa } #endif +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull end +#endif + #endif /* ZSTD_ERRORS_H_398273423 */ From 6611e408fe76fcb00272230bb4501ff2fad10ce3 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 16:58:39 -0700 Subject: [PATCH 06/11] Added nullability annotations for all the remaining parts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …primarily the static-library-specific bits, gated behind `ZSTD_STATIC_LINKING_ONLY` and `ZDICT_STATIC_LINKING_ONLY` that I didn't know about before. This now builds through Swift and C: * `make` * `make -C lib libzstd.a CC=clang MOREFLAGS='-Wnullability-completeness -Wnullable-to-nonnull-conversion -Werror'` * `swift build` * `swift build -Xcc -Wnullability-completeness -Xcc -Wnullable-to-nonnull-conversion -Xcc -Werror` Hopefully that covers all the permutations. --- lib/zdict.h | 8 +++++ lib/zstd.h | 98 +++++++++++++++++++++++++++++------------------------ 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/lib/zdict.h b/lib/zdict.h index 54f30d60bc5..8cee3a11b62 100644 --- a/lib/zdict.h +++ b/lib/zdict.h @@ -290,6 +290,10 @@ ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode); #if defined(ZDICT_STATIC_LINKING_ONLY) && !defined(ZSTD_ZDICT_H_STATIC) #define ZSTD_ZDICT_H_STATIC +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull begin +#endif + #if defined (__cplusplus) extern "C" { #endif @@ -488,4 +492,8 @@ size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize } #endif +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull end +#endif + #endif /* ZSTD_ZDICT_H_STATIC */ diff --git a/lib/zstd.h b/lib/zstd.h index 3db654b4b08..474c8c1978c 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -1255,6 +1255,10 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) #define ZSTD_H_ZSTD_STATIC_LINKING_ONLY +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull begin +#endif + #if defined (__cplusplus) extern "C" { #endif @@ -1764,7 +1768,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity * @return : number of bytes written or a ZSTD error. */ ZSTDLIB_STATIC_API size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, - unsigned* magicVariant, + unsigned* ZSTD_NULLABLE magicVariant, const void* src, size_t srcSize); /*! ZSTD_isSkippableFrame() : @@ -1866,22 +1870,22 @@ ZSTDLIB_STATIC_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadM * Limitation 2 : static cctx currently not compatible with multi-threading. * Limitation 3 : static dctx is incompatible with legacy support. */ -ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize); -ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */ +ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_NULLABLE ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_NULLABLE ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */ -ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize); -ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */ +ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_NULLABLE ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize); +ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_NULLABLE ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */ -ZSTDLIB_STATIC_API const ZSTD_CDict* ZSTD_initStaticCDict( +ZSTDLIB_STATIC_API const ZSTD_CDict* ZSTD_NULLABLE ZSTD_initStaticCDict( void* workspace, size_t workspaceSize, - const void* dict, size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams); -ZSTDLIB_STATIC_API const ZSTD_DDict* ZSTD_initStaticDDict( +ZSTDLIB_STATIC_API const ZSTD_DDict* ZSTD_NULLABLE ZSTD_initStaticDDict( void* workspace, size_t workspaceSize, - const void* dict, size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); @@ -1891,9 +1895,9 @@ ZSTDLIB_STATIC_API const ZSTD_DDict* ZSTD_initStaticDDict( * ZSTD_customMem is provided at creation time, using ZSTD_create*_advanced() variants listed below. * All allocation/free operations will be completed using these custom variants instead of regular ones. */ -typedef void* (*ZSTD_allocFunction) (void* opaque, size_t size); -typedef void (*ZSTD_freeFunction) (void* opaque, void* address); -typedef struct { ZSTD_allocFunction customAlloc; ZSTD_freeFunction customFree; void* opaque; } ZSTD_customMem; +typedef void* ZSTD_NULLABLE (*ZSTD_allocFunction) (void* ZSTD_NULLABLE opaque, size_t size); +typedef void (*ZSTD_freeFunction) (void* ZSTD_NULLABLE opaque, void* ZSTD_NULLABLE address); +typedef struct { ZSTD_allocFunction customAlloc; ZSTD_freeFunction customFree; void* ZSTD_NULLABLE opaque; } ZSTD_customMem; #if defined(__clang__) && __clang_major__ >= 5 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" @@ -1907,12 +1911,12 @@ ZSTD_customMem const ZSTD_defaultCMem = { NULL, NULL, NULL }; /**< this constan #pragma clang diagnostic pop #endif -ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem); -ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_createCStream_advanced(ZSTD_customMem customMem); -ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem); -ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_NULLABLE ZSTD_createCCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_NULLABLE ZSTD_createCStream_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_NULLABLE ZSTD_createDCtx_advanced(ZSTD_customMem customMem); +ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_NULLABLE ZSTD_createDStream_advanced(ZSTD_customMem customMem); -ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_t dictSize, +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_NULLABLE ZSTD_createCDict_advanced(const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_compressionParameters cParams, @@ -1929,23 +1933,23 @@ ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_ * ZSTD_freeThreadPool frees a thread pool, accepts NULL pointer. */ typedef struct POOL_ctx_s ZSTD_threadPool; -ZSTDLIB_STATIC_API ZSTD_threadPool* ZSTD_createThreadPool(size_t numThreads); -ZSTDLIB_STATIC_API void ZSTD_freeThreadPool (ZSTD_threadPool* pool); /* accept NULL pointer */ -ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* pool); +ZSTDLIB_STATIC_API ZSTD_threadPool* ZSTD_NULLABLE ZSTD_createThreadPool(size_t numThreads); +ZSTDLIB_STATIC_API void ZSTD_freeThreadPool (ZSTD_threadPool* ZSTD_NULLABLE pool); /* accept NULL pointer */ +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* ZSTD_NULLABLE pool); /* * This API is temporary and is expected to change or disappear in the future! */ -ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced2( - const void* dict, size_t dictSize, +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_NULLABLE ZSTD_createCDict_advanced2( + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, const ZSTD_CCtx_params* cctxParams, ZSTD_customMem customMem); -ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_advanced( - const void* dict, size_t dictSize, +ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_NULLABLE ZSTD_createDDict_advanced( + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType, ZSTD_customMem customMem); @@ -1961,7 +1965,7 @@ ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_advanced( * As a consequence, `dictBuffer` **must** outlive CDict, * and its content must remain unmodified throughout the lifetime of CDict. * note: equivalent to ZSTD_createCDict_advanced(), with dictLoadMethod==ZSTD_dlm_byRef */ -ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_byReference(const void* dictBuffer, size_t dictSize, int compressionLevel); +ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_NULLABLE ZSTD_createCDict_byReference(const void* ZSTD_NULLABLE dictBuffer, size_t dictSize, int compressionLevel); /*! ZSTD_getCParams() : * @return ZSTD_compressionParameters structure for a selected compression level and estimated srcSize. @@ -2016,7 +2020,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize, - const void* dict,size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_parameters params); /*! ZSTD_compress_usingCDict_advanced() : @@ -2035,18 +2039,18 @@ size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, /*! ZSTD_CCtx_loadDictionary_byReference() : * Same as ZSTD_CCtx_loadDictionary(), but dictionary content is referenced, instead of being copied into CCtx. * It saves some memory, but also requires that `dict` outlives its usage within `cctx` */ -ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE dict, size_t dictSize); /*! ZSTD_CCtx_loadDictionary_advanced() : * Same as ZSTD_CCtx_loadDictionary(), but gives finer control over * how to load the dictionary (by copy ? by reference ?) * and how to interpret it (automatic ? force raw mode ? full mode only ?) */ -ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); /*! ZSTD_CCtx_refPrefix_advanced() : * Same as ZSTD_CCtx_refPrefix(), but gives finer control over * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ -ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); /* === experimental parameters === */ /* these parameters can be used with ZSTD_setParameter() @@ -2406,8 +2410,8 @@ ZSTDLIB_STATIC_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cPa * This can be used with ZSTD_estimateCCtxSize_advanced_usingCCtxParams() * for static allocation of CCtx for single-threaded compression. */ -ZSTDLIB_STATIC_API ZSTD_CCtx_params* ZSTD_createCCtxParams(void); -ZSTDLIB_STATIC_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* params); /* accept NULL pointer */ +ZSTDLIB_STATIC_API ZSTD_CCtx_params* ZSTD_NULLABLE ZSTD_createCCtxParams(void); +ZSTDLIB_STATIC_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* ZSTD_NULLABLE params); /* accept NULL pointer */ /*! ZSTD_CCtxParams_reset() : * Reset params to default values. @@ -2482,26 +2486,26 @@ ZSTDLIB_STATIC_API unsigned ZSTD_isFrame(const void* buffer, size_t size); * Dictionary content is referenced, and therefore stays in dictBuffer. * It is important that dictBuffer outlives DDict, * it must remain read accessible throughout the lifetime of DDict */ -ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize); +ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_NULLABLE ZSTD_createDDict_byReference(const void* ZSTD_NULLABLE dictBuffer, size_t dictSize); /*! ZSTD_DCtx_loadDictionary_byReference() : * Same as ZSTD_DCtx_loadDictionary(), * but references `dict` content instead of copying it into `dctx`. * This saves memory if `dict` remains around., * However, it's imperative that `dict` remains accessible (and unmodified) while being used, so it must outlive decompression. */ -ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* ZSTD_NULLABLE dict, size_t dictSize); /*! ZSTD_DCtx_loadDictionary_advanced() : * Same as ZSTD_DCtx_loadDictionary(), * but gives direct control over * how to load the dictionary (by copy ? by reference ?) * and how to interpret it (automatic ? force raw mode ? full mode only ?). */ -ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType); /*! ZSTD_DCtx_refPrefix_advanced() : * Same as ZSTD_DCtx_refPrefix(), but gives finer control over * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */ -ZSTDLIB_STATIC_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); +ZSTDLIB_STATIC_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* ZSTD_NULLABLE prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType); /*! ZSTD_DCtx_setMaxWindowSize() : * Refuses allocating internal buffers for frames requiring a window size larger than provided limit. @@ -2682,7 +2686,7 @@ size_t ZSTD_initCStream_srcSize(ZSTD_CStream* zcs, ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ZSTDLIB_STATIC_API size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, - const void* dict, size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, int compressionLevel); /*! ZSTD_initCStream_advanced() : @@ -2700,7 +2704,7 @@ size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs, ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions") ZSTDLIB_STATIC_API size_t ZSTD_initCStream_advanced(ZSTD_CStream* zcs, - const void* dict, size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); @@ -2802,7 +2806,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx); * note: no dictionary will be used if dict == NULL or dictSize < 8 */ ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_loadDictionary, see zstd.h for detailed instructions") -ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize); +ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* ZSTD_NULLABLE dict, size_t dictSize); /*! * This function is deprecated, and is equivalent to: @@ -2953,10 +2957,10 @@ ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds); #define ZSTD_SEQUENCE_PRODUCER_ERROR ((size_t)(-1)) typedef size_t (*ZSTD_sequenceProducer_F) ( - void* sequenceProducerState, + void* ZSTD_NULLABLE sequenceProducerState, ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, const void* src, size_t srcSize, - const void* dict, size_t dictSize, + const void* ZSTD_NULLABLE dict, size_t dictSize, int compressionLevel, size_t windowSize ); @@ -2983,8 +2987,8 @@ typedef size_t (*ZSTD_sequenceProducer_F) ( ZSTDLIB_STATIC_API void ZSTD_registerSequenceProducer( ZSTD_CCtx* cctx, - void* sequenceProducerState, - ZSTD_sequenceProducer_F sequenceProducer + void* ZSTD_NULLABLE sequenceProducerState, + ZSTD_sequenceProducer_F ZSTD_NULLABLE sequenceProducer ); /*! ZSTD_CCtxParams_registerSequenceProducer() : @@ -2999,8 +3003,8 @@ ZSTD_registerSequenceProducer( ZSTDLIB_STATIC_API void ZSTD_CCtxParams_registerSequenceProducer( ZSTD_CCtx_params* params, - void* sequenceProducerState, - ZSTD_sequenceProducer_F sequenceProducer + void* ZSTD_NULLABLE sequenceProducerState, + ZSTD_sequenceProducer_F ZSTD_NULLABLE sequenceProducer ); @@ -3051,7 +3055,7 @@ ZSTD_CCtxParams_registerSequenceProducer( ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") ZSTDLIB_STATIC_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel); ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") -ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); +ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE dict, size_t dictSize, int compressionLevel); ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /**< note: fails if cdict==NULL */ @@ -3232,4 +3236,8 @@ ZSTDLIB_STATIC_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* bloc } #endif +#if ZSTD_NULLABILITY +#pragma clang assume_nonnull end +#endif + #endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */ From 4f7910e78858d11f1b8c46f501cb3a2edaa9c371 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 17:24:34 -0700 Subject: [PATCH 07/11] Some fixes for nullability annotations, for the amalgamated headers. --- build/single_file_libs/zstd-in.c | 11 +++++++++++ build/single_file_libs/zstddeclib-in.c | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/build/single_file_libs/zstd-in.c b/build/single_file_libs/zstd-in.c index f381ecc4f56..0aa4ac759ac 100644 --- a/build/single_file_libs/zstd-in.c +++ b/build/single_file_libs/zstd-in.c @@ -34,6 +34,17 @@ * * Note: multithreading is enabled for all platforms apart from Emscripten. */ +/* The amalgamation concatenates zstd's public headers (which carry + * nullability annotations) with the internal headers (which don't), in + * a single translation unit. That trips clang's `-Wnullability- + * completeness` on the unannotated internal declarations. Suppress + * the warning here, at the very top of the amalgam, so it covers every + * line that follows. */ +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wnullability-completeness" +#pragma clang diagnostic ignored "-Wnullability-extension" +#endif + #define DEBUGLEVEL 0 #define MEM_MODULE #undef XXH_NAMESPACE diff --git a/build/single_file_libs/zstddeclib-in.c b/build/single_file_libs/zstddeclib-in.c index 8d9c1f54bb7..e55eaf9fd17 100644 --- a/build/single_file_libs/zstddeclib-in.c +++ b/build/single_file_libs/zstddeclib-in.c @@ -32,6 +32,17 @@ * re-running without the "-x legacy/zstd_legacy.h" option (it excludes the * legacy support at the source level). */ +/* The amalgamation concatenates zstd's public headers (which carry + * nullability annotations) with the internal headers (which don't), in + * a single translation unit. That trips clang's `-Wnullability- + * completeness` on the unannotated internal declarations. Suppress + * the warning here, at the very top of the amalgam, so it covers every + * line that follows. */ +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wnullability-completeness" +#pragma clang diagnostic ignored "-Wnullability-extension" +#endif + #define DEBUGLEVEL 0 #define MEM_MODULE #undef XXH_NAMESPACE From f57387e5eb59cd811ac369b444e7b6268a10f95e Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 17:28:15 -0700 Subject: [PATCH 08/11] Added nullability annotations for the various functions which actually need it. I misunderstood how these functions all worked, initially - I didn't realise that the library uses things like a NULL `dst` (and a `size` of zero) to invoke special behaviour, like returning the size that would be necessary for the operation (instead of actually doing the operation). This is unfortunate since it makes for a significantly worse Swift API, but I expect that changing the libzstd C API is a non-starter. --- lib/zstd.h | 62 +++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/zstd.h b/lib/zstd.h index 474c8c1978c..4901018c37a 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -170,8 +170,8 @@ ZSTDLIB_API const char* ZSTD_versionString(void); * enough space to successfully compress the data. * @return : compressed size written into `dst` (<= `dstCapacity), * or an error code if it fails (which can be tested using ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity, - const void* src, size_t srcSize, +ZSTDLIB_API size_t ZSTD_compress( void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, int compressionLevel); /*! ZSTD_decompress() : @@ -183,8 +183,8 @@ ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity, * If maximum upper bound isn't known, prefer using streaming mode to decompress data. * @return : the number of bytes decompressed into `dst` (<= `dstCapacity`), * or an errorCode if it fails (which can be tested using ZSTD_isError()). */ -ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity, - const void* src, size_t compressedSize); +ZSTDLIB_API size_t ZSTD_decompress( void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t compressedSize); /*====== Decompression helper functions ======*/ @@ -303,8 +303,8 @@ ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* ZSTD_NULLABLE cctx * they will all be reset. Only @compressionLevel remains. */ ZSTDLIB_API size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, int compressionLevel); /*= Decompression context @@ -323,8 +323,8 @@ ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* ZSTD_NULLABLE dctx * Compatible with sticky parameters (see below). */ ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize); + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize); /********************************************* @@ -636,8 +636,8 @@ ZSTDLIB_API size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset); * or an error code if it fails (which can be tested using ZSTD_isError()). */ ZSTDLIB_API size_t ZSTD_compress2( ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize); + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize); /*********************************************** @@ -977,8 +977,8 @@ ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output * It's intended for a dictionary used only once. * Note 2 : When `dict == NULL || dictSize < 8` no dictionary is used. */ ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, const void* ZSTD_NULLABLE dict, size_t dictSize, int compressionLevel); @@ -989,8 +989,8 @@ ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx, * It's intended for a dictionary used only once. * Note : When `dict == NULL || dictSize < 8` no dictionary is used. */ ZSTDLIB_API size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, const void* ZSTD_NULLABLE dict, size_t dictSize); @@ -1026,8 +1026,8 @@ ZSTDLIB_API size_t ZSTD_freeCDict(ZSTD_CDict* ZSTD_NULLABLE CDict); * Note : compression level is _decided at dictionary creation time_, * and frame parameters are hardcoded (dictID=yes, contentSize=yes, checksum=no) */ ZSTDLIB_API size_t ZSTD_compress_usingCDict(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, const ZSTD_CDict* cdict); @@ -1048,9 +1048,9 @@ ZSTDLIB_API size_t ZSTD_freeDDict(ZSTD_DDict* ZSTD_NULLABLE ddict); * Decompression using a digested Dictionary. * Recommended when same dictionary is used multiple times. */ ZSTDLIB_API size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - const ZSTD_DDict* ddict); + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, + const ZSTD_DDict* ZSTD_NULLABLE ddict); /******************************** @@ -1767,7 +1767,7 @@ ZSTDLIB_STATIC_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity * * @return : number of bytes written or a ZSTD error. */ -ZSTDLIB_STATIC_API size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, +ZSTDLIB_STATIC_API size_t ZSTD_readSkippableFrame(void* ZSTD_NULLABLE dst, size_t dstCapacity, unsigned* ZSTD_NULLABLE magicVariant, const void* src, size_t srcSize); @@ -2018,8 +2018,8 @@ ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters p ZSTD_DEPRECATED("use ZSTD_compress2") ZSTDLIB_STATIC_API size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_parameters params); @@ -2030,8 +2030,8 @@ size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx, ZSTD_DEPRECATED("use ZSTD_compress2 with ZSTD_CCtx_loadDictionary") ZSTDLIB_STATIC_API size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, + void* ZSTD_NULLABLE dst, size_t dstCapacity, + const void* ZSTD_NULLABLE src, size_t srcSize, const ZSTD_CDict* cdict, ZSTD_frameParameters fParams); @@ -2465,8 +2465,8 @@ ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParametersUsingCCtxParams( */ ZSTDLIB_STATIC_API size_t ZSTD_compressStream2_simpleArgs ( ZSTD_CCtx* cctx, - void* dst, size_t dstCapacity, size_t* dstPos, - const void* src, size_t srcSize, size_t* srcPos, + void* ZSTD_NULLABLE dst, size_t dstCapacity, size_t* dstPos, + const void* ZSTD_NULLABLE src, size_t srcSize, size_t* srcPos, ZSTD_EndDirective endOp); @@ -2640,8 +2640,8 @@ size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format); */ ZSTDLIB_STATIC_API size_t ZSTD_decompressStream_simpleArgs ( ZSTD_DCtx* dctx, - void* dst, size_t dstCapacity, size_t* dstPos, - const void* src, size_t srcSize, size_t* srcPos); + void* ZSTD_NULLABLE dst, size_t dstCapacity, size_t* dstPos, + const void* ZSTD_NULLABLE src, size_t srcSize, size_t* srcPos); /******************************************************************** @@ -3064,14 +3064,14 @@ ZSTDLIB_STATIC_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /**< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */ ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") -ZSTDLIB_STATIC_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* ZSTD_NULLABLE dst, size_t dstCapacity, const void* ZSTD_NULLABLE src, size_t srcSize); ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.") -ZSTDLIB_STATIC_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); +ZSTDLIB_STATIC_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* ZSTD_NULLABLE dst, size_t dstCapacity, const void* ZSTD_NULLABLE src, size_t srcSize); /* The ZSTD_compressBegin_advanced() and ZSTD_compressBegin_usingCDict_advanced() are now DEPRECATED and will generate a compiler warning */ ZSTD_DEPRECATED("use advanced API to access custom parameters") ZSTDLIB_STATIC_API -size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /**< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */ +size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* ZSTD_NULLABLE dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /**< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */ ZSTD_DEPRECATED("use advanced API to access custom parameters") ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_CDict* const cdict, ZSTD_frameParameters const fParams, unsigned long long const pledgedSrcSize); /* compression parameters are already set within cdict. pledgedSrcSize must be correct. If srcSize is not known, use macro ZSTD_CONTENTSIZE_UNKNOWN */ From 999012f36573e817b3646fae4f951e770002e33d Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 17:32:30 -0700 Subject: [PATCH 09/11] Suppress the warnings about using Clang's nullability extensions. The choice to use the nullability extensions is deliberate, and has no downside for compilers which don't understand them (it's metadata for static analysis tools, essentially, not a semantic change at the C language or API level). --- lib/zstd_annotations.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/zstd_annotations.h b/lib/zstd_annotations.h index 40a58bb5ac8..2bdde3c0eff 100644 --- a/lib/zstd_annotations.h +++ b/lib/zstd_annotations.h @@ -68,6 +68,13 @@ #if ZSTD_NULLABILITY # define ZSTD_NULLABLE _Nullable + /* `_Nullable` is a clang language extension, so its mere presence + * trips `-Wnullability-extension` under `-Wpedantic`. The warning is + * not actionable for us — the whole point is to use the extension — + * so disable it for any TU that includes a zstd header. */ +# if defined(__clang__) +# pragma clang diagnostic ignored "-Wnullability-extension" +# endif #else # define ZSTD_NULLABLE #endif From ca4321d50b1382b8b99c94b583ea159d7a08903d Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 17:44:05 -0700 Subject: [PATCH 10/11] Added `zstd_annotations.h` to all the _other_ build systems. So many ways to build one little library! --- build/VS2008/fullbench/fullbench.vcproj | 4 ++++ build/VS2008/fuzzer/fuzzer.vcproj | 4 ++++ build/VS2008/zstd/zstd.vcproj | 4 ++++ build/VS2008/zstdlib/zstdlib.vcproj | 4 ++++ build/VS2010/fullbench/fullbench.vcxproj | 1 + build/VS2010/fuzzer/fuzzer.vcxproj | 1 + build/VS2010/libzstd-dll/libzstd-dll.vcxproj | 1 + build/VS2010/libzstd/libzstd.vcxproj | 1 + build/VS2010/zstd/zstd.vcxproj | 1 + build/meson/lib/meson.build | 3 ++- contrib/linux-kernel/Makefile | 5 +++++ lib/Makefile | 2 ++ 12 files changed, 30 insertions(+), 1 deletion(-) diff --git a/build/VS2008/fullbench/fullbench.vcproj b/build/VS2008/fullbench/fullbench.vcproj index 5e349dce956..eb254471036 100644 --- a/build/VS2008/fullbench/fullbench.vcproj +++ b/build/VS2008/fullbench/fullbench.vcproj @@ -462,6 +462,10 @@ RelativePath="..\..\..\lib\common\error_private.h" > + + diff --git a/build/VS2008/fuzzer/fuzzer.vcproj b/build/VS2008/fuzzer/fuzzer.vcproj index 32f28468473..703470de2d6 100644 --- a/build/VS2008/fuzzer/fuzzer.vcproj +++ b/build/VS2008/fuzzer/fuzzer.vcproj @@ -482,6 +482,10 @@ RelativePath="..\..\..\lib\common\error_private.h" > + + diff --git a/build/VS2008/zstd/zstd.vcproj b/build/VS2008/zstd/zstd.vcproj index fc87625a950..44a8fa36eda 100644 --- a/build/VS2008/zstd/zstd.vcproj +++ b/build/VS2008/zstd/zstd.vcproj @@ -582,6 +582,10 @@ RelativePath="..\..\..\lib\zstd.h" > + + diff --git a/build/VS2008/zstdlib/zstdlib.vcproj b/build/VS2008/zstdlib/zstdlib.vcproj index 61c88f5bcb4..146d70cb008 100644 --- a/build/VS2008/zstdlib/zstdlib.vcproj +++ b/build/VS2008/zstdlib/zstdlib.vcproj @@ -494,6 +494,10 @@ RelativePath="..\..\..\lib\common\error_private.h" > + + diff --git a/build/VS2010/fullbench/fullbench.vcxproj b/build/VS2010/fullbench/fullbench.vcxproj index c78a72eccdd..04bf3b809e9 100644 --- a/build/VS2010/fullbench/fullbench.vcxproj +++ b/build/VS2010/fullbench/fullbench.vcxproj @@ -192,6 +192,7 @@ + diff --git a/build/VS2010/fuzzer/fuzzer.vcxproj b/build/VS2010/fuzzer/fuzzer.vcxproj index ccbd2517de9..847019232bd 100644 --- a/build/VS2010/fuzzer/fuzzer.vcxproj +++ b/build/VS2010/fuzzer/fuzzer.vcxproj @@ -197,6 +197,7 @@ + diff --git a/build/VS2010/libzstd-dll/libzstd-dll.vcxproj b/build/VS2010/libzstd-dll/libzstd-dll.vcxproj index ddb8e3216f5..6a0bb718236 100644 --- a/build/VS2010/libzstd-dll/libzstd-dll.vcxproj +++ b/build/VS2010/libzstd-dll/libzstd-dll.vcxproj @@ -62,6 +62,7 @@ + diff --git a/build/VS2010/libzstd/libzstd.vcxproj b/build/VS2010/libzstd/libzstd.vcxproj index 09ead245a64..3a2f869c4e7 100644 --- a/build/VS2010/libzstd/libzstd.vcxproj +++ b/build/VS2010/libzstd/libzstd.vcxproj @@ -62,6 +62,7 @@ + diff --git a/build/VS2010/zstd/zstd.vcxproj b/build/VS2010/zstd/zstd.vcxproj index 230fd7d09e7..265b1595603 100644 --- a/build/VS2010/zstd/zstd.vcxproj +++ b/build/VS2010/zstd/zstd.vcxproj @@ -80,6 +80,7 @@ + diff --git a/build/meson/lib/meson.build b/build/meson/lib/meson.build index 81fc313f954..b37ba27e8ae 100644 --- a/build/meson/lib/meson.build +++ b/build/meson/lib/meson.build @@ -172,4 +172,5 @@ pkgconfig.generate(libzstd, install_headers(join_paths(zstd_rootdir, 'lib/zstd.h'), join_paths(zstd_rootdir, 'lib/zdict.h'), - join_paths(zstd_rootdir, 'lib/zstd_errors.h')) + join_paths(zstd_rootdir, 'lib/zstd_errors.h'), + join_paths(zstd_rootdir, 'lib/zstd_annotations.h')) diff --git a/contrib/linux-kernel/Makefile b/contrib/linux-kernel/Makefile index 63dd15d958f..1c4c26671b4 100644 --- a/contrib/linux-kernel/Makefile +++ b/contrib/linux-kernel/Makefile @@ -24,6 +24,7 @@ libzstd: --rewrite-include '=' \ --rewrite-include '"\.\./zstd.h"=' \ --rewrite-include '"(\.\./)?zstd_errors.h"=' \ + --rewrite-include '"(\.\./)?zstd_annotations.h"=' \ --sed 's,/\*\*\*,/* *,g' \ --sed 's,/\*\*,/*,g' \ --spdx \ @@ -59,6 +60,7 @@ libzstd: rm linux/lib/zstd/decompress/huf_decompress_amd64.S mv linux/lib/zstd/zstd.h linux/include/linux/zstd_lib.h mv linux/lib/zstd/zstd_errors.h linux/include/linux/ + mv linux/lib/zstd/zstd_annotations.h linux/include/linux/ cp linux_zstd.h linux/include/linux/zstd.h cp zstd_common_module.c linux/lib/zstd cp zstd_compress_module.c linux/lib/zstd @@ -72,10 +74,12 @@ LINUX ?= $(HOME)/repos/linux import: libzstd rm -f $(LINUX)/include/linux/zstd.h rm -f $(LINUX)/include/linux/zstd_errors.h + rm -f $(LINUX)/include/linux/zstd_annotations.h rm -rf $(LINUX)/lib/zstd cp linux/include/linux/zstd.h $(LINUX)/include/linux cp linux/include/linux/zstd_lib.h $(LINUX)/include/linux cp linux/include/linux/zstd_errors.h $(LINUX)/include/linux + cp linux/include/linux/zstd_annotations.h $(LINUX)/include/linux cp -r linux/lib/zstd $(LINUX)/lib import-upstream: @@ -86,6 +90,7 @@ import-upstream: cp -r ../../lib/compress $(LINUX)/lib/zstd cp -r ../../lib/decompress $(LINUX)/lib/zstd mv $(LINUX)/lib/zstd/zstd_errors.h $(LINUX)/include/linux + mv $(LINUX)/lib/zstd/zstd_annotations.h $(LINUX)/include/linux rm $(LINUX)/lib/zstd/common/threading.* rm $(LINUX)/lib/zstd/common/pool.* rm $(LINUX)/lib/zstd/common/xxhash.* diff --git a/lib/Makefile b/lib/Makefile index 569bd6090c9..e62161e769b 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -372,6 +372,7 @@ install-includes: @echo Installing includes $(INSTALL_DATA) zstd.h $(DESTDIR)$(INCLUDEDIR) $(INSTALL_DATA) zstd_errors.h $(DESTDIR)$(INCLUDEDIR) + $(INSTALL_DATA) zstd_annotations.h $(DESTDIR)$(INCLUDEDIR) $(INSTALL_DATA) zdict.h $(DESTDIR)$(INCLUDEDIR) .PHONY: uninstall @@ -383,6 +384,7 @@ uninstall: $(RM) $(DESTDIR)$(PKGCONFIGDIR)/libzstd.pc $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd.h $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd_errors.h + $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd_annotations.h $(RM) $(DESTDIR)$(INCLUDEDIR)/zdict.h @echo zstd libraries successfully uninstalled From efaefbb8d9089db9075f6ffd4e7393a6e7847797 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Sat, 16 May 2026 18:49:59 -0700 Subject: [PATCH 11/11] Added `zstd_annotations.h` to the file copy list in `build_library_test.sh` and `freestanding.py`. --- build/single_file_libs/build_library_test.sh | 1 + contrib/freestanding_lib/freestanding.py | 1 + 2 files changed, 2 insertions(+) diff --git a/build/single_file_libs/build_library_test.sh b/build/single_file_libs/build_library_test.sh index b67cd99a0f2..e0095968f9c 100755 --- a/build/single_file_libs/build_library_test.sh +++ b/build/single_file_libs/build_library_test.sh @@ -71,6 +71,7 @@ echo "Single file library creation script: PASSED" # Copy the header to here (for the tests) cp "$ZSTD_SRC_ROOT/zstd.h" examples/zstd.h cp "$ZSTD_SRC_ROOT/zstd_errors.h" examples/zstd_errors.h +cp "$ZSTD_SRC_ROOT/zstd_annotations.h" examples/zstd_annotations.h # Compile the generated output cc -Wall -Wextra -Werror -Wshadow -pthread -I. -Os -g0 -o $OUT_FILE zstd.c examples/roundtrip.c diff --git a/contrib/freestanding_lib/freestanding.py b/contrib/freestanding_lib/freestanding.py index df6983245d6..294b8253c8e 100755 --- a/contrib/freestanding_lib/freestanding.py +++ b/contrib/freestanding_lib/freestanding.py @@ -483,6 +483,7 @@ def _copy_source_lib(self): os.makedirs(self._dst_lib, exist_ok=True) self._copy_file("zstd.h") self._copy_file("zstd_errors.h") + self._copy_file("zstd_annotations.h") for subdir in INCLUDED_SUBDIRS: src_dir = os.path.join(self._src_lib, subdir) dst_dir = os.path.join(self._dst_lib, subdir)