diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index f21c5d6..89f73a5 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -1,6 +1,6 @@ # This GitHub workflow config has been generated by a script via # -# haskell-ci 'github' 'digest.cabal' +# haskell-ci 'github' '--distribution' 'focal' '--submodules' 'digest.cabal' # # To regenerate the script (for example after adjusting tested-with) run # @@ -8,9 +8,9 @@ # # For more information, see https://github.com/haskell-CI/haskell-ci # -# version: 0.15.20221107 +# version: 0.16.6 # -# REGENDATA ("0.15.20221107",["github","digest.cabal"]) +# REGENDATA ("0.16.6",["github","--distribution","focal","--submodules","digest.cabal"]) # name: Haskell-CI on: @@ -23,7 +23,7 @@ jobs: timeout-minutes: 60 container: - image: buildpack-deps:bionic + image: buildpack-deps:focal continue-on-error: ${{ matrix.allow-failure }} strategy: matrix: @@ -55,10 +55,10 @@ jobs: apt-get update apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 mkdir -p "$HOME/.ghcup/bin" - curl -sL https://downloads.haskell.org/ghcup/0.1.18.0/x86_64-linux-ghcup-0.1.18.0 > "$HOME/.ghcup/bin/ghcup" + curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup" chmod a+x "$HOME/.ghcup/bin/ghcup" "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) - "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) env: HCKIND: ${{ matrix.compilerKind }} HCNAME: ${{ matrix.compiler }} @@ -74,7 +74,7 @@ jobs: echo "HC=$HC" >> "$GITHUB_ENV" echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV" echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV" - echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" @@ -124,16 +124,17 @@ jobs: - name: install cabal-plan run: | mkdir -p $HOME/.cabal/bin - curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.6.2.0/cabal-plan-0.6.2.0-x86_64-linux.xz > cabal-plan.xz - echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c - + curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.7.3.0/cabal-plan-0.7.3.0-x86_64-linux.xz > cabal-plan.xz + echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c - xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan rm -f cabal-plan.xz chmod a+x $HOME/.cabal/bin/cabal-plan cabal-plan --version - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: source + submodules: "true" - name: initial cabal.project for sdist run: | touch cabal.project @@ -166,8 +167,8 @@ jobs: run: | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all cabal-plan - - name: cache - uses: actions/cache@v2 + - name: restore cache + uses: actions/cache/restore@v3 with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store @@ -188,8 +189,14 @@ jobs: ${CABAL} -vnormal check - name: haddock run: | - $CABAL v2-haddock --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all - name: unconstrained build run: | rm -f cabal.project.local $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all + - name: save cache + uses: actions/cache/save@v3 + if: always() + with: + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} + path: ~/.cabal/store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f5cb14b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/crc32c"] + path = external/crc32c + url = https://github.com/google/crc32c.git diff --git a/Data/Digest/CRC32C.hs b/Data/Digest/CRC32C.hs new file mode 100644 index 0000000..ea4ee70 --- /dev/null +++ b/Data/Digest/CRC32C.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnliftedFFITypes #-} + +module Data.Digest.CRC32C + ( CRC32C + , crc32c + , crc32cUpdate + ) where + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL +import qualified Data.ByteString.Short as BSS +import Data.ByteString.Unsafe (unsafeUseAsCStringLen) +import Data.Word +import Foreign.C.Types +import Foreign.Ptr +import GHC.Exts (ByteArray#) +import System.IO.Unsafe (unsafeDupablePerformIO) + +#if !MIN_VERSION_bytestring(0, 11, 1) +import qualified Data.ByteString.Short.Internal as BSS +#endif + +class CRC32C a where + -- | Compute CRC32C checksum + crc32c :: a -> Word32 + crc32c = crc32cUpdate 0 + + -- | Given the CRC32C checksum of a string, compute CRC32C of its + -- concatenation with another string (t.i., incrementally update + -- the CRC32C hash value) + crc32cUpdate :: Word32 -> a -> Word32 + +instance CRC32C BS.ByteString where + crc32c bs = unsafeDupablePerformIO $ + unsafeUseAsCStringLen bs $ \(ptr, len) -> + crc32c_value (castPtr ptr) (fromIntegral len) + + crc32cUpdate cks bs = unsafeDupablePerformIO $ + unsafeUseAsCStringLen bs $ \(ptr, len) -> + crc32c_extend cks (castPtr ptr) (fromIntegral len) + +instance CRC32C BL.ByteString where + crc32cUpdate = BL.foldlChunks crc32cUpdate + +instance CRC32C [Word8] where + crc32cUpdate n = (crc32cUpdate n) . BL.pack + +instance CRC32C BSS.ShortByteString where + crc32c sbs@(BSS.SBS ba#) = unsafeDupablePerformIO $ + -- Must be unsafe ffi + crc32c_value' ba# (fromIntegral $ BSS.length sbs) + + crc32cUpdate cks sbs@(BSS.SBS ba#) = unsafeDupablePerformIO $ + -- Must be unsafe ffi + crc32c_extend' cks ba# (fromIntegral $ BSS.length sbs) + +------------------------------------------------------------------------------- + +foreign import ccall unsafe "crc32c/crc32c.h crc32c_value" + crc32c_value :: Ptr Word8 -> CSize -> IO Word32 + +foreign import ccall unsafe "crc32c/crc32c.h crc32c_extend" + crc32c_extend :: Word32 -> Ptr Word8 -> CSize -> IO Word32 + +foreign import ccall unsafe "crc32c/crc32c.h crc32c_value" + crc32c_value' :: ByteArray# -> CSize -> IO Word32 + +foreign import ccall unsafe "crc32c/crc32c.h crc32c_extend" + crc32c_extend' :: Word32 -> ByteArray# -> CSize -> IO Word32 diff --git a/digest.cabal b/digest.cabal index fbb2391..33d5990 100644 --- a/digest.cabal +++ b/digest.cabal @@ -1,51 +1,132 @@ -name: digest -version: 0.0.1.7 -x-revision: 1 -copyright: (c) 2009 Eugene Kirpichov -license: BSD2 -license-file: LICENSE -author: Eugene Kirpichov -maintainer: Eugene Kirpichov -category: Cryptography -synopsis: CRC32 and Adler32 hashes for bytestrings -description: This package provides efficient hash implementations for - strict and lazy bytestrings. For now, CRC32 and Adler32 are supported; - they are implemented as FFI bindings to efficient code from zlib. -stability: provisional -build-type: Simple -cabal-version: >= 1.10 -tested-with: - GHC==8.10.7 - , GHC==9.0.2 - , GHC==9.2.5 - , GHC==9.4.3 +cabal-version: 2.4 +name: digest +version: 0.0.1.7 +x-revision: 1 +copyright: (c) 2009 Eugene Kirpichov +license: BSD-2-Clause +license-file: LICENSE +author: Eugene Kirpichov +maintainer: Eugene Kirpichov +category: Cryptography +synopsis: CRC32 and Adler32 hashes for bytestrings +description: + This package provides efficient hash implementations for + strict and lazy bytestrings. For now, CRC32 and Adler32 are supported; + they are implemented as FFI bindings to efficient code from zlib. +stability: provisional +build-type: Simple +tested-with: GHC ==8.10.7 || ==9.0.2 || ==9.2.5 || ==9.4.3 extra-source-files: + CHANGELOG.md + external/crc32c/include/crc32c/crc32c.h + external/crc32c/LICENSE + external/crc32c/src/*.h + include/crc32c/crc32c_config.h testing/trivial-reference.c testing/trivial.expected testing/trivial.hs - CHANGELOG.md flag pkg-config default: True manual: True description: Use @pkg-config(1)@ to locate @zlib@ library. +-- TODO: auto detect +flag have_builtin_prefetch + default: False + manual: True + description: The cxx compiler has the __builtin_prefetch intrinsic. + +-- TODO: auto detect +flag have_mm_prefetch + default: False + manual: True + description: + Targeting X86 and the compiler has the _mm_prefetch intrinsic. + +-- TODO: auto detect +flag have_sse42 + default: False + manual: True + description: + Can be enabled to improve performance of CRC32C if targeting X86 and + the compiler has the _mm_crc32_u{8,32,64} intrinsics. + +-- TODO: auto detect +flag have_arm64_crc32c + default: False + manual: True + description: + Targeting ARM and the compiler has the __crc32c{b,h,w,d} and the + vmull_p64 intrinsics. + +-- TODO: auto detect +flag have_strong_getauxval + default: False + manual: True + description: + The system libraries have the getauxval function in the header. + Should be true on Linux and Android API level 20+. + +-- TODO: auto detect +flag have_weak_getauxval + default: False + manual: True + description: + The compiler supports defining getauxval as a weak symbol. + Should be true for any compiler that supports __attribute__((weak)). + source-repository head - type: git + type: git location: https://github.com/TeofilC/digest library - exposed-modules: Data.Digest.CRC32, - Data.Digest.Adler32 - default-extensions: CPP, ForeignFunctionInterface - default-language: Haskell2010 - build-depends: - base < 5 - , bytestring >= 0.9 && < 0.13 - includes: zlib.h - ghc-options: -Wall - if flag(pkg-config) && !os(windows) && !os(freebsd) + exposed-modules: + Data.Digest.Adler32 + Data.Digest.CRC32 + Data.Digest.CRC32C + + default-extensions: + CPP + ForeignFunctionInterface + + default-language: Haskell2010 + build-depends: + , base >=4.12 && <5 + , bytestring >=0.10 && <0.13 + + includes: zlib.h + include-dirs: include external/crc32c/include + cxx-options: -std=c++11 + cxx-sources: + external/crc32c/src/crc32c.cc + external/crc32c/src/crc32c_portable.cc + + if flag(have_builtin_prefetch) + cxx-options: -DHAVE_BUILTIN_PREFETCH + + if flag(have_mm_prefetch) + cxx-options: -DHAVE_MM_PREFETCH + + if (arch(x86_64) && flag(have_sse42)) + cxx-options: -DHAVE_SSE42 -msse4.2 + cxx-sources: external/crc32c/src/crc32c_sse42.cc + + if (arch(aarch64) && flag(have_arm64_crc32c)) + cxx-options: -DHAVE_ARM64_CRC32C + cxx-sources: external/crc32c/src/crc32c_arm64.cc + + if flag(have_strong_getauxval) + cxx-options: -DHAVE_STRONG_GETAUXVAL + + if flag(have_weak_getauxval) + cxx-options: -DHAVE_WEAK_GETAUXVAL + + ghc-options: -Wall + + if ((flag(pkg-config) && !os(windows)) && !os(freebsd)) pkgconfig-depends: zlib + else build-depends: zlib diff --git a/external/crc32c b/external/crc32c new file mode 160000 index 0000000..21fc8ef --- /dev/null +++ b/external/crc32c @@ -0,0 +1 @@ +Subproject commit 21fc8ef30415a635e7351ffa0e5d5367943d4a94 diff --git a/include/crc32c/crc32c_config.h b/include/crc32c/crc32c_config.h new file mode 100644 index 0000000..0228d56 --- /dev/null +++ b/include/crc32c/crc32c_config.h @@ -0,0 +1,49 @@ +// Also see: external/crc32c/src/crc32c_config.h.in +#ifndef CRC32C_CRC32C_CONFIG_H_ +#define CRC32C_CRC32C_CONFIG_H_ + +// From GHC +#include + +#ifdef WORDS_BIGENDIAN +// Define to 1 if building for a big-endian platform. +#define BYTE_ORDER_BIG_ENDIAN 1 +#else +#define BYTE_ORDER_BIG_ENDIAN 0 +#endif + +// Set by cabal flag 'have_builtin_prefetch' +// +// Define to 1 if the compiler has the __builtin_prefetch intrinsic. +//#define HAVE_BUILTIN_PREFETCH 0 + +// Set by cabal flag 'have_mm_prefetch' +// +// Define to 1 if targeting X86 and the compiler has the _mm_prefetch intrinsic. +//#define HAVE_MM_PREFETCH 0 + +// Set by cabal flag 'have_sse42' +// +// Define to 1 if targeting X86 and the compiler has the _mm_crc32_u{8,32,64} +// intrinsics. +//#define HAVE_SSE42 0 + +// Set by cabal flag 'have_arm64_crc32c' +// +// Define to 1 if targeting ARM and the compiler has the __crc32c{b,h,w,d} and +// the vmull_p64 intrinsics. +//#define HAVE_ARM64_CRC32C 0 + +// Set by cabal flag 'have_strong_getauxval' +// +// Define to 1 if the system libraries have the getauxval function in the +// header. Should be true on Linux and Android API level 20+. +//#define HAVE_STRONG_GETAUXVAL 0 + +// Set by cabal flag 'have_weak_getauxval' +// +// Define to 1 if the compiler supports defining getauxval as a weak symbol. +// Should be true for any compiler that supports __attribute__((weak)). +//#define HAVE_WEAK_GETAUXVAL 0 + +#endif // CRC32C_CRC32C_CONFIG_H_ diff --git a/testing/crc32c/crc32c_config.h b/testing/crc32c/crc32c_config.h new file mode 100644 index 0000000..46eb047 --- /dev/null +++ b/testing/crc32c/crc32c_config.h @@ -0,0 +1,41 @@ +#ifndef CRC32C_CRC32C_CONFIG_H_ +#define CRC32C_CRC32C_CONFIG_H_ + +// Define to 1 if building for a big-endian platform. +//#define BYTE_ORDER_BIG_ENDIAN 0 + +// Set by cabal flag 'have_builtin_prefetch' +// +// Define to 1 if the compiler has the __builtin_prefetch intrinsic. +//#define HAVE_BUILTIN_PREFETCH 0 + +// Set by cabal flag 'have_mm_prefetch' +// +// Define to 1 if targeting X86 and the compiler has the _mm_prefetch intrinsic. +//#define HAVE_MM_PREFETCH 0 + +// Set by cabal flag 'have_sse42' +// +// Define to 1 if targeting X86 and the compiler has the _mm_crc32_u{8,32,64} +// intrinsics. +//#define HAVE_SSE42 0 + +// Set by cabal flag 'have_arm64_crc32c' +// +// Define to 1 if targeting ARM and the compiler has the __crc32c{b,h,w,d} and +// the vmull_p64 intrinsics. +//#define HAVE_ARM64_CRC32C 0 + +// Set by cabal flag 'have_strong_getauxval' +// +// Define to 1 if the system libraries have the getauxval function in the +// header. Should be true on Linux and Android API level 20+. +//#define HAVE_STRONG_GETAUXVAL 0 + +// Set by cabal flag 'have_weak_getauxval' +// +// Define to 1 if the compiler supports defining getauxval as a weak symbol. +// Should be true for any compiler that supports __attribute__((weak)). +//#define HAVE_WEAK_GETAUXVAL 0 + +#endif // CRC32C_CRC32C_CONFIG_H_ diff --git a/testing/trivial-reference.c b/testing/trivial-reference.c index 73aa227..84e5f74 100644 --- a/testing/trivial-reference.c +++ b/testing/trivial-reference.c @@ -1,5 +1,13 @@ +/** + * Build: + * + * gcc -I./testing -I. -I./include -Iexternal/crc32c/include \ + * testing/trivial-reference.c external/crc32c/src/crc32c.cc \ + * external/crc32c/src/crc32c_portable.cc -lz + */ #include #include +#include "crc32c/crc32c.h" typedef uLong HashFunc(uLong seed, const Bytef *buf, uInt len); @@ -45,5 +53,11 @@ int main(void) runTest("crc32Update 123", crc32, 123); runTest("crc32Update 0xFFFFFFFF", crc32, 0xFFFFFFFF); + runTest("crc32c", crc32c_extend, 0); + runTest("crc32cUpdate 0", crc32c_extend, 0); + runTest("crc32cUpdate 1", crc32c_extend, 1); + runTest("crc32cUpdate 123", crc32c_extend, 123); + runTest("crc32cUpdate 0xFFFFFFFF", crc32c_extend, 0xFFFFFFFF); + return 0; } diff --git a/testing/trivial.expected b/testing/trivial.expected index 4363755..622d1fe 100644 --- a/testing/trivial.expected +++ b/testing/trivial.expected @@ -78,3 +78,43 @@ crc32Update 0xFFFFFFFF 265137764 3681351380 +crc32c + 0 + 0 + 1383945041 + 3251651376 + 2591144780 + 2621708363 + +crc32cUpdate 0 + 0 + 0 + 1383945041 + 3251651376 + 2591144780 + 2621708363 + +crc32cUpdate 1 + 1 + 1 + 2685849682 + 867942451 + 2724254944 + 1089823471 + +crc32cUpdate 123 + 123 + 123 + 3127684199 + 700562438 + 3036593667 + 3432906049 + +crc32cUpdate 0xFFFFFFFF + 4294967295 + 4294967295 + 4294967295 + 1817374622 + 553398918 + 3687405092 + diff --git a/testing/trivial.hs b/testing/trivial.hs index f2e689b..9f3adfe 100644 --- a/testing/trivial.hs +++ b/testing/trivial.hs @@ -1,5 +1,6 @@ import Data.Digest.Adler32 import Data.Digest.CRC32 +import Data.Digest.CRC32C import Control.Monad (forM_) import Data.ByteString (ByteString) @@ -50,3 +51,9 @@ main = do runTest "crc32Update 1" $ crc32Update 1 runTest "crc32Update 123" $ crc32Update 123 runTest "crc32Update 0xFFFFFFFF" $ crc32Update 0xFFFFFFFF + + runTest "crc32c" $ crc32c + runTest "crc32cUpdate 0" $ crc32cUpdate 0 + runTest "crc32cUpdate 1" $ crc32cUpdate 1 + runTest "crc32cUpdate 123" $ crc32cUpdate 123 + runTest "crc32cUpdate 0xFFFFFFFF" $ crc32cUpdate 0xFFFFFFFF