diff --git a/.github/workflows/ubuntu-c-cpp-ci.yml b/.github/workflows/ubuntu-c-cpp-ci.yml new file mode 100644 index 000000000..31a731e1b --- /dev/null +++ b/.github/workflows/ubuntu-c-cpp-ci.yml @@ -0,0 +1,51 @@ +name: Ubuntu-C-Cpp-CI + +'on': + - push + - pull_request + +permissions: + contents: read + +jobs: + ci: + name: ubuntu-${{ matrix.compiler.cc }}-library-cpp-${{ matrix.mode.library }}-tests-cpp-${{ matrix.mode.tests }}-shared-${{ matrix.mode.shared }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + compiler: + - { cc: gcc, cxx: g++ } + - { cc: clang, cxx: clang++ } + mode: + - { library: 'OFF', tests: 'ON', shared: 'OFF' } + - { library: 'ON', tests: 'OFF', shared: 'OFF' } + - { library: 'ON', tests: 'ON', shared: 'OFF' } + - { library: 'ON', tests: 'OFF', shared: 'ON' } + env: + CC: ${{ matrix.compiler.cc }} + CXX: ${{ matrix.compiler.cxx }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Build and test + run: | + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_ROARING_TESTS=ON -DENABLE_ROARING_BENCHMARKS=OFF \ + -DBUILD_SHARED_LIBS=${{ matrix.mode.shared }} -DROARING_BUILD_STATIC=ON \ + -DROARING_BUILD_C_AS_CPP=${{ matrix.mode.library }} \ + -DROARING_BUILD_C_TESTS_AS_CPP=${{ matrix.mode.tests }} + cmake --build build --parallel 2 + ctest --test-dir build --output-on-failure --no-tests=error + - name: Compare C and C++ function symbols + if: matrix.mode.library == 'ON' && matrix.mode.tests == 'OFF' + run: | + cmake -S . -B build-c -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_ROARING_TESTS=OFF -DENABLE_ROARING_BENCHMARKS=OFF -DROARING_USE_CPM=OFF \ + -DBUILD_SHARED_LIBS=${{ matrix.mode.shared }} -DROARING_BUILD_STATIC=ON \ + -DROARING_BUILD_C_AS_CPP=OFF + cmake --build build-c --target roaring --parallel 2 + if [[ '${{ matrix.mode.shared }}' == ON ]]; then + bash tools/check-c-symbols.sh build-c/libroaring.so build/libroaring.so + else + bash tools/check-c-symbols.sh build-c/src/libroaring.a build/src/libroaring.a + fi diff --git a/include/roaring/portability.h b/include/roaring/portability.h index b6983c376..044d55b11 100644 --- a/include/roaring/portability.h +++ b/include/roaring/portability.h @@ -327,6 +327,23 @@ static inline int roaring_hamming(uint64_t x) { } // extern "C" { #endif // __cplusplus +/* + * Emit a callable definition for an external inline function defined in a + * header. Use this once in its implementation file, never for static inline + * helpers. C's extern inline declaration emits the definition; C++ needs an + * externally visible reference even when every call in the library is inlined. + * The constant pointer provides that reference without compiler-specific flags + * or attributes. Its croaring_inline_ symbol is an implementation detail. + */ +#ifdef __cplusplus +#define CROARING_EXPORT_INLINE(result, name, args) \ + extern inline result name args; \ + extern const auto croaring_inline_##name = &name +#else +#define CROARING_EXPORT_INLINE(result, name, args) \ + extern inline result name args +#endif + // this is almost standard? #undef STRINGIFY_IMPLEMENTATION_ #undef STRINGIFY diff --git a/src/array_util.c b/src/array_util.c index 60669249b..d3a586aea 100644 --- a/src/array_util.c +++ b/src/array_util.c @@ -27,8 +27,9 @@ namespace roaring { namespace internal { #endif -extern inline int32_t binarySearch(const uint16_t *array, int32_t lenarray, - uint16_t ikey); +CROARING_EXPORT_INLINE(int32_t, binarySearch, + (const uint16_t *array, int32_t lenarray, + uint16_t ikey)); #if CROARING_IS_X64 // used by intersect_vector16 @@ -2034,9 +2035,11 @@ static inline __m512i avx512_cx16(__m512i v, __m512i t, __mmask32 hi) { static inline __m512i avx512_bitonic_sort32(__m512i v) { v = avx512_cx16(v, _mm512_shuffle_i64x2(v, v, 0x4E), 0xFFFF0000u); // d=16 v = avx512_cx16(v, _mm512_shuffle_i64x2(v, v, 0xB1), 0xFF00FF00u); // d=8 - v = avx512_cx16(v, _mm512_shuffle_epi32(v, 0x4E), 0xF0F0F0F0u); // d=4 - v = avx512_cx16(v, _mm512_shuffle_epi32(v, 0xB1), 0xCCCCCCCCu); // d=2 - v = avx512_cx16(v, _mm512_rol_epi32(v, 16), 0xAAAAAAAAu); // d=1 + // d=4, then d=2. GCC declares this immediate as _MM_PERM_ENUM, and C++ + // does not convert an int to it, so use the named constants. + v = avx512_cx16(v, _mm512_shuffle_epi32(v, _MM_PERM_BADC), 0xF0F0F0F0u); + v = avx512_cx16(v, _mm512_shuffle_epi32(v, _MM_PERM_CDAB), 0xCCCCCCCCu); + v = avx512_cx16(v, _mm512_rol_epi32(v, 16), 0xAAAAAAAAu); // d=1 return v; } diff --git a/src/bitset.c b/src/bitset.c index 0ef181b4f..9f030261c 100644 --- a/src/bitset.c +++ b/src/bitset.c @@ -11,22 +11,25 @@ #ifdef __cplusplus extern "C" { namespace roaring { -namespace internal { +namespace api { #endif -extern inline void bitset_print(const bitset_t *b); -extern inline bool bitset_for_each(const bitset_t *b, bitset_iterator iterator, - void *ptr); -extern inline size_t bitset_next_set_bits(const bitset_t *bitset, - size_t *buffer, size_t capacity, - size_t *startfrom); -extern inline void bitset_set_to_value(bitset_t *bitset, size_t i, bool flag); -extern inline bool bitset_next_set_bit(const bitset_t *bitset, size_t *i); -extern inline void bitset_set(bitset_t *bitset, size_t i); -extern inline bool bitset_get(const bitset_t *bitset, size_t i); -extern inline size_t bitset_size_in_words(const bitset_t *bitset); -extern inline size_t bitset_size_in_bits(const bitset_t *bitset); -extern inline size_t bitset_size_in_bytes(const bitset_t *bitset); +CROARING_EXPORT_INLINE(void, bitset_print, (const bitset_t *b)); +CROARING_EXPORT_INLINE(bool, bitset_for_each, + (const bitset_t *b, bitset_iterator iterator, + void *ptr)); +CROARING_EXPORT_INLINE(size_t, bitset_next_set_bits, + (const bitset_t *bitset, size_t *buffer, size_t capacity, + size_t *startfrom)); +CROARING_EXPORT_INLINE(void, bitset_set_to_value, + (bitset_t * bitset, size_t i, bool flag)); +CROARING_EXPORT_INLINE(bool, bitset_next_set_bit, + (const bitset_t *bitset, size_t *i)); +CROARING_EXPORT_INLINE(void, bitset_set, (bitset_t * bitset, size_t i)); +CROARING_EXPORT_INLINE(bool, bitset_get, (const bitset_t *bitset, size_t i)); +CROARING_EXPORT_INLINE(size_t, bitset_size_in_words, (const bitset_t *bitset)); +CROARING_EXPORT_INLINE(size_t, bitset_size_in_bits, (const bitset_t *bitset)); +CROARING_EXPORT_INLINE(size_t, bitset_size_in_bytes, (const bitset_t *bitset)); /* Create a new bitset. Return NULL in case of failure. */ bitset_t *bitset_create(void) { @@ -507,5 +510,5 @@ bool bitset_trim(bitset_t *bitset) { #ifdef __cplusplus } } -} // extern "C" { namespace roaring { namespace internal { +} // extern "C" { namespace roaring { namespace api { #endif diff --git a/src/containers/array.c b/src/containers/array.c index 93cfbeaae..931d945e2 100644 --- a/src/containers/array.c +++ b/src/containers/array.c @@ -22,28 +22,23 @@ namespace roaring { namespace internal { #endif -extern inline uint16_t array_container_minimum(const array_container_t *arr); -extern inline uint16_t array_container_maximum(const array_container_t *arr); -extern inline int array_container_index_equalorlarger( - const array_container_t *arr, uint16_t x); - -extern inline int array_container_rank(const array_container_t *arr, - uint16_t x); -extern inline uint32_t array_container_rank_many(const array_container_t *arr, - uint64_t start_rank, - const uint32_t *begin, - const uint32_t *end, - uint64_t *ans); -extern inline int array_container_get_index(const array_container_t *arr, - uint16_t x); -extern inline bool array_container_contains(const array_container_t *arr, - uint16_t pos); -extern inline int array_container_cardinality(const array_container_t *array); -extern inline bool array_container_nonzero_cardinality( - const array_container_t *array); -extern inline int32_t array_container_serialized_size_in_bytes(int32_t card); -extern inline bool array_container_empty(const array_container_t *array); -extern inline bool array_container_full(const array_container_t *array); +CROARING_EXPORT_INLINE(uint16_t, array_container_minimum, + (const array_container_t *arr)); +CROARING_EXPORT_INLINE(uint16_t, array_container_maximum, + (const array_container_t *arr)); +CROARING_EXPORT_INLINE(int, array_container_index_equalorlarger, + (const array_container_t *arr, uint16_t x)); + +CROARING_EXPORT_INLINE(int, array_container_rank, + (const array_container_t *arr, uint16_t x)); +CROARING_EXPORT_INLINE(uint32_t, array_container_rank_many, + (const array_container_t *arr, uint64_t start_rank, + const uint32_t *begin, const uint32_t *end, + uint64_t *ans)); +CROARING_EXPORT_INLINE(int, array_container_get_index, + (const array_container_t *arr, uint16_t x)); +CROARING_EXPORT_INLINE(bool, array_container_contains, + (const array_container_t *arr, uint16_t pos)); /* Create a new array with capacity size. Return NULL in case of failure. */ array_container_t *array_container_create_given_capacity(int32_t size) { diff --git a/src/containers/bitset.c b/src/containers/bitset.c index 4442245b7..70e3a29c5 100644 --- a/src/containers/bitset.c +++ b/src/containers/bitset.c @@ -31,22 +31,10 @@ namespace roaring { namespace internal { #endif -extern inline int bitset_container_cardinality( - const bitset_container_t *bitset); -extern inline void bitset_container_set(bitset_container_t *bitset, - uint16_t pos); -// unused at this time: -// extern inline void bitset_container_unset(bitset_container_t *bitset, -// uint16_t pos); -extern inline bool bitset_container_get(const bitset_container_t *bitset, - uint16_t pos); -extern inline int32_t bitset_container_serialized_size_in_bytes(void); -extern inline bool bitset_container_add(bitset_container_t *bitset, - uint16_t pos); -extern inline bool bitset_container_remove(bitset_container_t *bitset, - uint16_t pos); -extern inline bool bitset_container_contains(const bitset_container_t *bitset, - uint16_t pos); +CROARING_EXPORT_INLINE(bool, bitset_container_get, + (const bitset_container_t *bitset, uint16_t pos)); +CROARING_EXPORT_INLINE(bool, bitset_container_contains, + (const bitset_container_t *bitset, uint16_t pos)); void bitset_container_clear(bitset_container_t *bitset) { memset(bitset->words, 0, sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); @@ -1353,4 +1341,4 @@ int bitset_container_index_equalorlarger(const bitset_container_t *container, ui #endif #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop -#endif \ No newline at end of file +#endif diff --git a/src/containers/containers.c b/src/containers/containers.c index b65326900..fe6659db0 100644 --- a/src/containers/containers.c +++ b/src/containers/containers.c @@ -17,43 +17,15 @@ static inline uint32_t minimum_uint32(uint32_t a, uint32_t b) { return (a < b) ? a : b; } -extern inline const container_t *container_unwrap_shared( - const container_t *candidate_shared_container, uint8_t *type); +CROARING_EXPORT_INLINE(bool, container_iterator_next, + (const container_t *c, uint8_t typecode, + roaring_container_iterator_t *it, uint16_t *value)); -extern inline container_t *container_mutable_unwrap_shared( - container_t *candidate_shared_container, uint8_t *type); - -extern inline int container_get_cardinality(const container_t *c, - uint8_t typecode); - -extern inline container_t *container_iand(container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_ior(container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_ixor(container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_iandnot(container_t *c1, uint8_t type1, - const container_t *c2, - uint8_t type2, - uint8_t *result_type); - -extern bool container_iterator_next(const container_t *c, uint8_t typecode, - roaring_container_iterator_t *it, - uint16_t *value); - -extern bool container_iterator_prev(const container_t *c, uint8_t typecode, - roaring_container_iterator_t *it, - uint16_t *value); -extern bool container_contains( - const container_t *c, uint16_t val, - uint8_t typecode // !!! should be second argument? -); +CROARING_EXPORT_INLINE(bool, container_iterator_prev, + (const container_t *c, uint8_t typecode, + roaring_container_iterator_t *it, uint16_t *value)); +CROARING_EXPORT_INLINE(bool, container_contains, + (const container_t *c, uint16_t val, uint8_t typecode)); void container_free(container_t *c, uint8_t type) { switch (type) { @@ -151,32 +123,6 @@ bool container_internal_validate(const container_t *container, uint8_t typecode, } } -extern inline bool container_nonzero_cardinality(const container_t *c, - uint8_t typecode); - -extern inline int container_to_uint32_array(uint32_t *output, - const container_t *c, - uint8_t typecode, uint32_t base); - -extern inline container_t *container_add(container_t *c, uint16_t val, - uint8_t typecode, // !!! 2nd arg? - uint8_t *new_typecode); - -extern inline bool container_contains(const container_t *c, uint16_t val, - uint8_t typecode); // !!! 2nd arg? - -extern inline container_t *container_and(const container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_or(const container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_xor(const container_t *c1, uint8_t type1, - const container_t *c2, uint8_t type2, - uint8_t *result_type); - container_t *get_copy_of_container(container_t *c, uint8_t *typecode, bool copy_on_write) { if (copy_on_write) { @@ -261,45 +207,6 @@ void shared_container_free(shared_container_t *container) { } } -extern inline container_t *container_not(const container_t *c1, uint8_t type1, - uint8_t *result_type); - -extern inline container_t *container_not_range(const container_t *c1, - uint8_t type1, - uint32_t range_start, - uint32_t range_end, - uint8_t *result_type); - -extern inline container_t *container_inot(container_t *c1, uint8_t type1, - uint8_t *result_type); - -extern inline container_t *container_inot_range(container_t *c1, uint8_t type1, - uint32_t range_start, - uint32_t range_end, - uint8_t *result_type); - -extern inline container_t *container_range_of_ones(uint32_t range_start, - uint32_t range_end, - uint8_t *result_type); - -// where are the correponding things for union and intersection?? -extern inline container_t *container_lazy_xor(const container_t *c1, - uint8_t type1, - const container_t *c2, - uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_lazy_ixor(container_t *c1, uint8_t type1, - const container_t *c2, - uint8_t type2, - uint8_t *result_type); - -extern inline container_t *container_andnot(const container_t *c1, - uint8_t type1, - const container_t *c2, - uint8_t type2, - uint8_t *result_type); - CROARING_ALLOW_UNALIGNED roaring_container_iterator_t container_init_iterator(const container_t *c, uint8_t typecode, diff --git a/src/containers/run.c b/src/containers/run.c index 596ade121..4a536d53e 100644 --- a/src/containers/run.c +++ b/src/containers/run.c @@ -21,20 +21,16 @@ namespace roaring { namespace internal { #endif -extern inline uint16_t run_container_minimum(const run_container_t *run); -extern inline uint16_t run_container_maximum(const run_container_t *run); -extern inline int32_t interleavedBinarySearch(const rle16_t *array, - int32_t lenarray, uint16_t ikey); -extern inline bool run_container_contains(const run_container_t *run, - uint16_t pos); -extern inline int run_container_index_equalorlarger(const run_container_t *arr, - uint16_t x); -extern inline bool run_container_is_full(const run_container_t *run); -extern inline bool run_container_nonzero_cardinality(const run_container_t *rc); -extern inline int32_t run_container_serialized_size_in_bytes(int32_t num_runs); -extern inline run_container_t *run_container_create_range(uint32_t start, - uint32_t stop); -extern inline int run_container_cardinality(const run_container_t *run); +CROARING_EXPORT_INLINE(uint16_t, run_container_minimum, + (const run_container_t *run)); +CROARING_EXPORT_INLINE(uint16_t, run_container_maximum, + (const run_container_t *run)); +CROARING_EXPORT_INLINE(int32_t, interleavedBinarySearch, + (const rle16_t *array, int32_t lenarray, uint16_t ikey)); +CROARING_EXPORT_INLINE(bool, run_container_contains, + (const run_container_t *run, uint16_t pos)); +CROARING_EXPORT_INLINE(int, run_container_index_equalorlarger, + (const run_container_t *arr, uint16_t x)); bool run_container_add(run_container_t *run, uint16_t pos) { int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos); diff --git a/src/roaring.c b/src/roaring.c index e348c024c..a344993f2 100644 --- a/src/roaring.c +++ b/src/roaring.c @@ -18,25 +18,33 @@ using namespace ::roaring::internal; extern "C" { +#endif + +CROARING_EXPORT_INLINE(int, roaring_trailing_zeroes, + (unsigned long long input_num)); +CROARING_EXPORT_INLINE(int, roaring_leading_zeroes, + (unsigned long long input_num)); + +#ifdef __cplusplus namespace roaring { namespace api { #endif #define CROARING_SERIALIZATION_ARRAY_UINT32 1 #define CROARING_SERIALIZATION_CONTAINER 2 -extern inline bool roaring_bitmap_contains(const roaring_bitmap_t *r, - uint32_t val); -extern inline int roaring_trailing_zeroes(unsigned long long input_num); -extern inline int roaring_leading_zeroes(unsigned long long input_num); -extern inline void roaring_bitmap_init_cleared(roaring_bitmap_t *r); -extern inline bool roaring_bitmap_get_copy_on_write(const roaring_bitmap_t *r); -extern inline void roaring_bitmap_set_copy_on_write(roaring_bitmap_t *r, - bool cow); -extern inline roaring_bitmap_t *roaring_bitmap_create(void); -extern inline void roaring_bitmap_add_range(roaring_bitmap_t *r, uint64_t min, - uint64_t max); -extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, - uint64_t min, uint64_t max); +CROARING_EXPORT_INLINE(bool, roaring_bitmap_contains, + (const roaring_bitmap_t *r, uint32_t val)); +CROARING_EXPORT_INLINE(void, roaring_bitmap_init_cleared, + (roaring_bitmap_t * r)); +CROARING_EXPORT_INLINE(bool, roaring_bitmap_get_copy_on_write, + (const roaring_bitmap_t *r)); +CROARING_EXPORT_INLINE(void, roaring_bitmap_set_copy_on_write, + (roaring_bitmap_t * r, bool cow)); +CROARING_EXPORT_INLINE(roaring_bitmap_t *, roaring_bitmap_create, (void)); +CROARING_EXPORT_INLINE(void, roaring_bitmap_add_range, + (roaring_bitmap_t * r, uint64_t min, uint64_t max)); +CROARING_EXPORT_INLINE(void, roaring_bitmap_remove_range, + (roaring_bitmap_t * r, uint64_t min, uint64_t max)); static inline bool is_cow(const roaring_bitmap_t *r) { return r->high_low_container.flags & ROARING_FLAG_COW; diff --git a/src/roaring64.c b/src/roaring64.c index 709e67129..da0439041 100644 --- a/src/roaring64.c +++ b/src/roaring64.c @@ -3269,8 +3269,10 @@ void roaring64_iterator_free(roaring64_iterator_t *it) { roaring_free(it); } CROARING_STATIC_ASSERT(offsetof(roaring64_iterator_t, pub) == 0, "the public members must be first in the iterator"); -extern inline bool roaring64_iterator_has_value(const roaring64_iterator_t *it); -extern inline uint64_t roaring64_iterator_value(const roaring64_iterator_t *it); +CROARING_EXPORT_INLINE(bool, roaring64_iterator_has_value, + (const roaring64_iterator_t *it)); +CROARING_EXPORT_INLINE(uint64_t, roaring64_iterator_value, + (const roaring64_iterator_t *it)); // The current container is exhausted: step the ART to the next leaf. static inline bool roaring64_iterator_next_leaf(roaring64_iterator_t *it) { diff --git a/src/roaring_array.c b/src/roaring_array.c index fecbba079..573dcd72a 100644 --- a/src/roaring_array.c +++ b/src/roaring_array.c @@ -19,25 +19,21 @@ namespace internal { // Convention: [0,ra->size) all elements are initialized // [ra->size, ra->allocation_size) is junk and contains nothing needing freeing -extern inline int32_t ra_get_size(const roaring_array_t *ra); -extern inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x); +CROARING_EXPORT_INLINE(int32_t, ra_get_size, (const roaring_array_t *ra)); +CROARING_EXPORT_INLINE(int32_t, ra_get_index, + (const roaring_array_t *ra, uint16_t x)); -extern inline container_t *ra_get_container_at_index(const roaring_array_t *ra, - uint16_t i, - uint8_t *typecode); +CROARING_EXPORT_INLINE(container_t *, ra_get_container_at_index, + (const roaring_array_t *ra, uint16_t i, + uint8_t *typecode)); -extern inline void ra_unshare_container_at_index(roaring_array_t *ra, - uint16_t i); +CROARING_EXPORT_INLINE(void, ra_replace_key_and_container_at_index, + (roaring_array_t * ra, int32_t i, uint16_t key, + container_t *c, uint8_t typecode)); -extern inline void ra_replace_key_and_container_at_index(roaring_array_t *ra, - int32_t i, - uint16_t key, - container_t *c, - uint8_t typecode); - -extern inline void ra_set_container_at_index(const roaring_array_t *ra, - int32_t i, container_t *c, - uint8_t typecode); +CROARING_EXPORT_INLINE(void, ra_set_container_at_index, + (const roaring_array_t *ra, int32_t i, container_t *c, + uint8_t typecode)); static bool realloc_array(roaring_array_t *ra, int32_t new_capacity) { // @@ -324,17 +320,8 @@ container_t *ra_get_container(roaring_array_t *ra, uint16_t x, return ra->containers[i]; } -extern inline container_t *ra_get_container_at_index(const roaring_array_t *ra, - uint16_t i, - uint8_t *typecode); - -extern inline uint16_t ra_get_key_at_index(const roaring_array_t *ra, - uint16_t i); - -extern inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x); - -extern inline int32_t ra_advance_until(const roaring_array_t *ra, uint16_t x, - int32_t pos); +CROARING_EXPORT_INLINE(uint16_t, ra_get_key_at_index, + (const roaring_array_t *ra, uint16_t i)); // everything skipped over is freed int32_t ra_advance_until_freeing(roaring_array_t *ra, uint16_t x, int32_t pos) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 399129067..195499e15 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,15 @@ add_cpp_test(cpp_example1) add_cpp_test(cpp_example2) add_cpp_test(cpp_readme_example) add_c_test(c_example1) +add_c_test(c_inline_linkage_unit) +# Exercise C callers even when the other C tests are compiled as C++. +set_source_files_properties(c_inline_linkage_unit.c PROPERTIES LANGUAGE C) +# Link an unoptimized C caller to the library even in Release builds. +if(MSVC) + target_compile_options(c_inline_linkage_unit PRIVATE /Od) +else() + target_compile_options(c_inline_linkage_unit PRIVATE -O0) +endif() add_c_test(c_readme_example) add_c_test(cbitset_unit) add_c_test(array_container_unit) diff --git a/tests/c_inline_linkage_unit.c b/tests/c_inline_linkage_unit.c new file mode 100644 index 000000000..3f4703d80 --- /dev/null +++ b/tests/c_inline_linkage_unit.c @@ -0,0 +1,50 @@ +#include + +#include "test.h" + +int main(void) { + // Volatile function pointers require external C definitions even when + // optimization could otherwise inline these calls from the headers. + void (*volatile add_range)(roaring_bitmap_t *, uint64_t, uint64_t) = + roaring_bitmap_add_range; + bool (*volatile contains)(const roaring_bitmap_t *, uint32_t) = + roaring_bitmap_contains; + void (*volatile set_bit)(bitset_t *, size_t) = bitset_set; + bool (*volatile get_bit)(const bitset_t *, size_t) = bitset_get; + bool (*volatile next)(const container_t *, uint8_t, + roaring_container_iterator_t *, uint16_t *) = + container_iterator_next; + bool (*volatile prev)(const container_t *, uint8_t, + roaring_container_iterator_t *, uint16_t *) = + container_iterator_prev; + + roaring_bitmap_t *bitmap = roaring_bitmap_create(); + assert_non_null(bitmap); + add_range(bitmap, 10, 20); + assert_int_equal(roaring_bitmap_get_cardinality(bitmap), 10); + assert_true(contains(bitmap, 10)); + assert_false(contains(bitmap, 20)); + roaring_bitmap_free(bitmap); + + bitset_t *bits = bitset_create(); + assert_non_null(bits); + set_bit(bits, 70); + assert_true(get_bit(bits, 70)); + assert_false(get_bit(bits, 69)); + bitset_free(bits); + + array_container_t *array = array_container_create_range(10, 13); + assert_non_null(array); + const container_t *container = (const container_t *)array; + uint16_t value = 0; + roaring_container_iterator_t it = + container_init_iterator(container, ARRAY_CONTAINER_TYPE, &value); + assert_int_equal(value, 10); + assert_true(next(container, ARRAY_CONTAINER_TYPE, &it, &value)); + assert_int_equal(value, 11); + assert_true(prev(container, ARRAY_CONTAINER_TYPE, &it, &value)); + assert_int_equal(value, 10); + assert_false(prev(container, ARRAY_CONTAINER_TYPE, &it, &value)); + array_container_free(array); + return 0; +} diff --git a/tests/cbitset_unit.c b/tests/cbitset_unit.c index 3bccaa76b..8a72fb91c 100644 --- a/tests/cbitset_unit.c +++ b/tests/cbitset_unit.c @@ -4,6 +4,10 @@ #include +#ifdef __cplusplus +using namespace roaring::api; +#endif + #include "test.h" int compute_cardinality(bitset_t *b) { diff --git a/tests/robust_deserialization_unit.c b/tests/robust_deserialization_unit.c index ff4f543f5..33db70cec 100644 --- a/tests/robust_deserialization_unit.c +++ b/tests/robust_deserialization_unit.c @@ -123,8 +123,9 @@ DEFINE_TEST(test_robust_deserialize7) { test_deserialize(TEST_DATA_DIR "crashproneinput7.bin"); } -static void invalid_deserialize_test(const void* data, size_t size, +static void invalid_deserialize_test(const void* input, size_t size, const char* description) { + const char* data = (const char*)input; // Ensure that the data _looks_ like a valid bitmap, but is not. size_t serialized_size = roaring_bitmap_portable_deserialize_size(data, size); @@ -148,7 +149,8 @@ static void invalid_deserialize_test(const void* data, size_t size, assert_null(bitmap); } -static void valid_deserialize_test(const void* data, size_t size) { +static void valid_deserialize_test(const void* input, size_t size) { + const char* data = (const char*)input; // Ensure that the data _looks_ like a valid bitmap, but is not. size_t serialized_size = roaring_bitmap_portable_deserialize_size(data, size); diff --git a/tests/test.h b/tests/test.h index 2845b626b..9cd9157d4 100644 --- a/tests/test.h +++ b/tests/test.h @@ -11,7 +11,11 @@ #include #include +#include + #ifdef __cplusplus +using roaring::misc::tellmeall; + // // It's generally not good to span a header file with `extern "C"`, but // this is how the cpp_unit.cpp test was doing it. cmocka.h apparently diff --git a/tests/toplevel_unit.c b/tests/toplevel_unit.c index ec5ab8049..1fd5d792f 100644 --- a/tests/toplevel_unit.c +++ b/tests/toplevel_unit.c @@ -101,7 +101,7 @@ DEFINE_TEST(issue433) { roaring_bitmap_add_range_closed(b1, 258047, 262143); roaring_bitmap_remove_range_closed(b1, 262143, 262143); size_t len = roaring_bitmap_portable_size_in_bytes(b1); - char *data = roaring_malloc(len); + char *data = (char *)roaring_malloc(len); roaring_bitmap_portable_serialize(b1, data); roaring_bitmap_t *b2 = roaring_bitmap_portable_deserialize_safe(data, len); assert_true(roaring_bitmap_equals(b1, b2)); @@ -119,7 +119,7 @@ DEFINE_TEST(issue436) { roaring_bitmap_printf_describe(b1); roaring_bitmap_remove_range_closed(b1, 6143, 65505); size_t len = roaring_bitmap_portable_size_in_bytes(b1); - char *data = roaring_malloc(len); + char *data = (char *)roaring_malloc(len); roaring_bitmap_portable_serialize(b1, data); roaring_bitmap_t *b2 = roaring_bitmap_portable_deserialize_safe(data, len); assert_true(roaring_bitmap_equals(b1, b2)); @@ -4345,8 +4345,8 @@ void read_backward_compare(roaring_bitmap_t *r, const uint32_t *ref_values, uint32_t ref_count, uint32_t step) { roaring_uint32_iterator_t *iter = roaring_iterator_create(r); roaring_iterator_init_last(r, iter); - uint32_t *buffer = - malloc(sizeof(uint32_t) * (step == UINT32_MAX ? 65536 : step)); + uint32_t *buffer = (uint32_t *)malloc(sizeof(uint32_t) * + (step == UINT32_MAX ? 65536 : step)); uint32_t remaining = ref_count; while (remaining > 0) { assert_true(iter->has_value == true); @@ -6061,7 +6061,7 @@ DEFINE_TEST(robust_deserialization) { DEFINE_TEST(fuzz_deserializer) { for (size_t i = 0; i < 10000; i++) { size_t vec_size = our_rand() % 10000; - char *buffer = malloc(vec_size); + char *buffer = (char *)malloc(vec_size); for (size_t j = 0; j < vec_size; j++) { buffer[j] = our_rand() % 256; } diff --git a/tools/check-c-symbols.sh b/tools/check-c-symbols.sh new file mode 100755 index 000000000..8e80137d8 --- /dev/null +++ b/tools/check-c-symbols.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Check that a C++ library defines every global function in the C library. +# Requires GNU nm. Exits 1 for missing symbols and 2 for invalid input. +set -euo pipefail + +if [[ $# != 2 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT + +function_symbols() { + local options=(-g --defined-only --format=posix) + case "$1" in + *.so|*.so.*) options+=(-D) ;; + esac + # Compare names, allowing strong, weak and indirect function definitions. + # Data symbols and C++ implementation details need not match the C build. + nm "${options[@]}" "$1" | + awk '$2 ~ /^[TWiI]$/ { print $1 }' | LC_ALL=C sort -u +} + +if ! function_symbols "$1" > "$scratch/c" || + ! function_symbols "$2" > "$scratch/cpp"; then + echo "Could not read library symbols." >&2 + exit 2 +fi +if [[ ! -s "$scratch/c" ]]; then + echo "No global C functions found in $1" >&2 + exit 2 +fi +LC_ALL=C comm -23 "$scratch/c" "$scratch/cpp" > "$scratch/missing" +if [[ -s "$scratch/missing" ]]; then + echo "C functions missing from the C++ library:" >&2 + cat "$scratch/missing" >&2 + exit 1 +fi +echo "All $(wc -l < "$scratch/c" | tr -d ' ') C functions are defined in the C++ library."