From 82b41179abfbe5a366c94fedf8e7f3ea3761eb24 Mon Sep 17 00:00:00 2001 From: Konstantin Bogdanov Date: Wed, 21 Oct 2026 17:25:25 +0200 Subject: [PATCH 1/2] Make `_sincos` reach libm's `sincos` on every Linux libc, and use it in `cagnoli` `_sincos` used `__builtin_sincos`, which Clang turns into the `llvm.sincos` intrinsic. LLVM lowers that intrinsic to a `sincos` call only for the C libraries it knows to have one (glibc, Android, Fuchsia) and splits it back into separate `sin` and `cos` calls for the others, musl among them, so on a musl build the helper saved nothing. A plain call to a function named `sincos` is recognized and lowered the same way. Declare the function under another C name with the `sincos` assembler label instead: the compiler does not recognize it, and the call reaches libm as written under Clang, GCC and ThinLTO. Every Linux libc provides `sincos`; other platforms keep the builtin. `cagnoli` (the cell area functions) still took `sin` and `cos` of three angles separately; with the helper fixed, use it there too. `h3CellAreaM2` in ClickHouse was 20% slower on aarch64 with musl because of these calls. All 316 tests pass; the results are unchanged. Co-Authored-By: Claude Fable 5.1 --- src/apps/applib/include/args.h | 4 +-- src/apps/benchmarks/benchmarkGridPathCells.c | 10 +++----- src/apps/benchmarks/benchmarkH3Api.c | 5 ++-- src/apps/benchmarks/benchmarkVertex.c | 5 ++-- src/h3lib/include/h3Index.h | 27 +++++++++++--------- src/h3lib/include/mathExtensions.h | 22 +++++++++++++--- src/h3lib/lib/area.c | 12 ++++++--- 7 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/apps/applib/include/args.h b/src/apps/applib/include/args.h index 511913836c..038038b035 100644 --- a/src/apps/applib/include/args.h +++ b/src/apps/applib/include/args.h @@ -90,7 +90,7 @@ int _parseArgsList(int argc, char *argv[], int numArgs, Arg *args[], // common arguments #define ARG_HELP \ - { .names = {"-h", "--help"}, .helpText = "Show this help message." } + {.names = {"-h", "--help"}, .helpText = "Show this help message."} #define DEFINE_INDEX_ARG(varName, argName) \ H3Index varName = 0; \ Arg argName = { \ @@ -109,7 +109,7 @@ int _parseArgsList(int argc, char *argv[], int numArgs, Arg *args[], .value = &varName, \ .helpText = "H3 Cell"} #define ARG_KML \ - { .names = {"-k", "--kml"}, .helpText = "Print output in KML format." } + {.names = {"-k", "--kml"}, .helpText = "Print output in KML format."} #define DEFINE_KML_NAME_ARG(varName, argName) \ char varName[BUFF_SIZE] = {0}; \ Arg argName = { \ diff --git a/src/apps/benchmarks/benchmarkGridPathCells.c b/src/apps/benchmarks/benchmarkGridPathCells.c index 2fcbaeb92a..333fe967e2 100644 --- a/src/apps/benchmarks/benchmarkGridPathCells.c +++ b/src/apps/benchmarks/benchmarkGridPathCells.c @@ -27,12 +27,10 @@ int64_t size; H3_EXPORT(gridPathCellsSize)(startIndex, endFar, &size); H3Index *out = calloc(size, sizeof(H3Index)); -BENCHMARK(gridPathCellsNear, 10000, { - H3_EXPORT(gridPathCells)(startIndex, endNear, out); -}); -BENCHMARK(gridPathCellsFar, 1000, { - H3_EXPORT(gridPathCells)(startIndex, endFar, out); -}); +BENCHMARK(gridPathCellsNear, 10000, + { H3_EXPORT(gridPathCells)(startIndex, endNear, out); }); +BENCHMARK(gridPathCellsFar, 1000, + { H3_EXPORT(gridPathCells)(startIndex, endFar, out); }); free(out); diff --git a/src/apps/benchmarks/benchmarkH3Api.c b/src/apps/benchmarks/benchmarkH3Api.c index d54b665974..22ec3ca8f7 100644 --- a/src/apps/benchmarks/benchmarkH3Api.c +++ b/src/apps/benchmarks/benchmarkH3Api.c @@ -31,8 +31,7 @@ BENCHMARK(latLngToCell, 10000, { H3_EXPORT(latLngToCell)(&coord, 9, &h); }); BENCHMARK(cellToLatLng, 10000, { H3_EXPORT(cellToLatLng)(hex, &outCoord); }); -BENCHMARK(cellToBoundary, 10000, { - H3_EXPORT(cellToBoundary)(hex, &outBoundary); -}); +BENCHMARK(cellToBoundary, 10000, + { H3_EXPORT(cellToBoundary)(hex, &outBoundary); }); END_BENCHMARKS(); diff --git a/src/apps/benchmarks/benchmarkVertex.c b/src/apps/benchmarks/benchmarkVertex.c index a0bd6c01dc..38e90cff0b 100644 --- a/src/apps/benchmarks/benchmarkVertex.c +++ b/src/apps/benchmarks/benchmarkVertex.c @@ -44,9 +44,8 @@ H3Index *vertexes = calloc(6, sizeof(H3Index)); BENCHMARK(cellToVertexes, 10000, { H3_EXPORT(cellToVertexes)(hex, vertexes); }); -BENCHMARK(cellToVertexesPent, 10000, { - H3_EXPORT(cellToVertexes)(pentagon, vertexes); -}); +BENCHMARK(cellToVertexesPent, 10000, + { H3_EXPORT(cellToVertexes)(pentagon, vertexes); }); BENCHMARK(cellToVertexesRing, 10000, { for (int i = 0; i < ring2Count; i++) { diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index a90574e1f8..184a14690d 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -92,47 +92,50 @@ /** * Gets the highest bit of the H3 index. */ -#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) +#define H3_GET_HIGH_BIT(h3) \ + ((int)((((h3) & H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) /** * Sets the highest bit of the h3 to v. */ -#define H3_SET_HIGH_BIT(h3, v) \ - (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ +#define H3_SET_HIGH_BIT(h3, v) \ + (h3) = (((h3) & H3_HIGH_BIT_MASK_NEGATIVE) | \ (((uint64_t)(v)) << H3_MAX_OFFSET)) /** * Gets the integer mode of h3. */ -#define H3_GET_MODE(h3) ((int)((((h3)&H3_MODE_MASK) >> H3_MODE_OFFSET))) +#define H3_GET_MODE(h3) ((int)((((h3) & H3_MODE_MASK) >> H3_MODE_OFFSET))) /** * Sets the integer mode of h3 to v. */ #define H3_SET_MODE(h3, v) \ - (h3) = (((h3)&H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) + (h3) = \ + (((h3) & H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) /** * Gets the integer base cell of h3. */ -#define H3_GET_BASE_CELL(h3) ((int)((((h3)&H3_BC_MASK) >> H3_BC_OFFSET))) +#define H3_GET_BASE_CELL(h3) ((int)((((h3) & H3_BC_MASK) >> H3_BC_OFFSET))) /** * Sets the integer base cell of h3 to bc. */ #define H3_SET_BASE_CELL(h3, bc) \ - (h3) = (((h3)&H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) + (h3) = (((h3) & H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) /** * Gets the integer resolution of h3. */ -#define H3_GET_RESOLUTION(h3) ((int)((((h3)&H3_RES_MASK) >> H3_RES_OFFSET))) +#define H3_GET_RESOLUTION(h3) ((int)((((h3) & H3_RES_MASK) >> H3_RES_OFFSET))) /** * Sets the integer resolution of h3. */ #define H3_SET_RESOLUTION(h3, res) \ - (h3) = (((h3)&H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) + (h3) = \ + (((h3) & H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) /** * Gets the resolution res integer digit (0-7) of h3. @@ -145,15 +148,15 @@ * Sets a value in the reserved space. Setting to non-zero may produce invalid * indexes. */ -#define H3_SET_RESERVED_BITS(h3, v) \ - (h3) = (((h3)&H3_RESERVED_MASK_NEGATIVE) | \ +#define H3_SET_RESERVED_BITS(h3, v) \ + (h3) = (((h3) & H3_RESERVED_MASK_NEGATIVE) | \ (((uint64_t)(v)) << H3_RESERVED_OFFSET)) /** * Gets a value in the reserved space. Should always be zero for valid indexes. */ #define H3_GET_RESERVED_BITS(h3) \ - ((int)((((h3)&H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) + ((int)((((h3) & H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) /** * Sets the resolution res digit of h3 to the integer digit (0-7) diff --git a/src/h3lib/include/mathExtensions.h b/src/h3lib/include/mathExtensions.h index 1b09a0ee28..2bba52386a 100644 --- a/src/h3lib/include/mathExtensions.h +++ b/src/h3lib/include/mathExtensions.h @@ -35,12 +35,26 @@ * Many H3 coordinate transforms need both sin(x) and cos(x) of the same angle. * Computing them together lets the math library share the argument reduction * and polynomial evaluation, which is roughly twice as fast as two independent - * sin()/cos() calls, while returning identical values. The compiler builtin - * lowers to a single libm `sincos` call where one exists and falls back to - * separate sin()/cos() otherwise, so this stays portable. + * sin()/cos() calls, while returning identical values. + * + * On Linux the libm `sincos` is called directly. `__builtin_sincos`, a + * `sin`/`cos` pair the optimizer merges by itself, and even a plain call to + * a function named `sincos` all become the `llvm.sincos` intrinsic, which + * LLVM lowers to a `sincos` call only for the C libraries it knows to have + * one (glibc, Android, Fuchsia) and splits back into separate `sin` and + * `cos` calls for the others, musl among them. Declaring the function under + * another C name with the `sincos` assembler label keeps the compiler from + * recognizing it, so the call reaches libm as written. `sincos` is a GNU + * extension that every Linux libc provides. Elsewhere the builtin is used. */ +#if defined(__linux__) +void _h3Sincos(double x, double *s, double *c) __asm__("sincos"); +#endif + static inline void _sincos(double x, double *s, double *c) { -#if defined(__GNUC__) || defined(__clang__) +#if defined(__linux__) + _h3Sincos(x, s, c); +#elif defined(__GNUC__) || defined(__clang__) __builtin_sincos(x, s, c); #else *s = sin(x); diff --git a/src/h3lib/lib/area.c b/src/h3lib/lib/area.c index f46e86dff6..b079062400 100644 --- a/src/h3lib/lib/area.c +++ b/src/h3lib/lib/area.c @@ -26,6 +26,7 @@ #include "constants.h" #include "h3Assert.h" #include "h3api.h" +#include "mathExtensions.h" // Cagnoli contribution for edge arc x to y, following d3-geo’s // area implementation: @@ -34,8 +35,11 @@ static inline double cagnoli(LatLng x, LatLng y) { x.lat = x.lat / 2.0 + M_PI / 4.0; y.lat = y.lat / 2.0 + M_PI / 4.0; - double sa = sin(x.lat) * sin(y.lat); - double ca = cos(x.lat) * cos(y.lat); + double sinXLat, cosXLat, sinYLat, cosYLat; + _sincos(x.lat, &sinXLat, &cosXLat); + _sincos(y.lat, &sinYLat, &cosYLat); + double sa = sinXLat * sinYLat; + double ca = cosXLat * cosYLat; // For an edge crossing the antimeridian, the raw difference is close to // +-2*pi, and the true (tiny) longitude step is lost to the ulp of 2*pi. @@ -46,8 +50,8 @@ static inline double cagnoli(LatLng x, LatLng y) { if (d > M_PI || d < -M_PI) { d = (y.lng - copysign(M_PI, y.lng)) - (x.lng - copysign(M_PI, x.lng)); } - double sd = sin(d); - double cd = cos(d); + double sd, cd; + _sincos(d, &sd, &cd); return -2.0 * atan2(sa * sd, sa * cd + ca); } From bdeced881633b10c132ac72b6fba80ba892e62ab Mon Sep 17 00:00:00 2001 From: Konstantin Bogdanov Date: Wed, 21 Oct 2026 17:29:41 +0200 Subject: [PATCH 2/2] Revert formatting changes to unrelated files The previous commit picked up `clang-format` output that the build applied in place to files this change does not touch. Co-Authored-By: Claude Opus 5.5 --- src/apps/applib/include/args.h | 4 +-- src/apps/benchmarks/benchmarkGridPathCells.c | 10 +++++--- src/apps/benchmarks/benchmarkH3Api.c | 5 ++-- src/apps/benchmarks/benchmarkVertex.c | 5 ++-- src/h3lib/include/h3Index.h | 27 +++++++++----------- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/apps/applib/include/args.h b/src/apps/applib/include/args.h index 038038b035..511913836c 100644 --- a/src/apps/applib/include/args.h +++ b/src/apps/applib/include/args.h @@ -90,7 +90,7 @@ int _parseArgsList(int argc, char *argv[], int numArgs, Arg *args[], // common arguments #define ARG_HELP \ - {.names = {"-h", "--help"}, .helpText = "Show this help message."} + { .names = {"-h", "--help"}, .helpText = "Show this help message." } #define DEFINE_INDEX_ARG(varName, argName) \ H3Index varName = 0; \ Arg argName = { \ @@ -109,7 +109,7 @@ int _parseArgsList(int argc, char *argv[], int numArgs, Arg *args[], .value = &varName, \ .helpText = "H3 Cell"} #define ARG_KML \ - {.names = {"-k", "--kml"}, .helpText = "Print output in KML format."} + { .names = {"-k", "--kml"}, .helpText = "Print output in KML format." } #define DEFINE_KML_NAME_ARG(varName, argName) \ char varName[BUFF_SIZE] = {0}; \ Arg argName = { \ diff --git a/src/apps/benchmarks/benchmarkGridPathCells.c b/src/apps/benchmarks/benchmarkGridPathCells.c index 333fe967e2..2fcbaeb92a 100644 --- a/src/apps/benchmarks/benchmarkGridPathCells.c +++ b/src/apps/benchmarks/benchmarkGridPathCells.c @@ -27,10 +27,12 @@ int64_t size; H3_EXPORT(gridPathCellsSize)(startIndex, endFar, &size); H3Index *out = calloc(size, sizeof(H3Index)); -BENCHMARK(gridPathCellsNear, 10000, - { H3_EXPORT(gridPathCells)(startIndex, endNear, out); }); -BENCHMARK(gridPathCellsFar, 1000, - { H3_EXPORT(gridPathCells)(startIndex, endFar, out); }); +BENCHMARK(gridPathCellsNear, 10000, { + H3_EXPORT(gridPathCells)(startIndex, endNear, out); +}); +BENCHMARK(gridPathCellsFar, 1000, { + H3_EXPORT(gridPathCells)(startIndex, endFar, out); +}); free(out); diff --git a/src/apps/benchmarks/benchmarkH3Api.c b/src/apps/benchmarks/benchmarkH3Api.c index 22ec3ca8f7..d54b665974 100644 --- a/src/apps/benchmarks/benchmarkH3Api.c +++ b/src/apps/benchmarks/benchmarkH3Api.c @@ -31,7 +31,8 @@ BENCHMARK(latLngToCell, 10000, { H3_EXPORT(latLngToCell)(&coord, 9, &h); }); BENCHMARK(cellToLatLng, 10000, { H3_EXPORT(cellToLatLng)(hex, &outCoord); }); -BENCHMARK(cellToBoundary, 10000, - { H3_EXPORT(cellToBoundary)(hex, &outBoundary); }); +BENCHMARK(cellToBoundary, 10000, { + H3_EXPORT(cellToBoundary)(hex, &outBoundary); +}); END_BENCHMARKS(); diff --git a/src/apps/benchmarks/benchmarkVertex.c b/src/apps/benchmarks/benchmarkVertex.c index 38e90cff0b..a0bd6c01dc 100644 --- a/src/apps/benchmarks/benchmarkVertex.c +++ b/src/apps/benchmarks/benchmarkVertex.c @@ -44,8 +44,9 @@ H3Index *vertexes = calloc(6, sizeof(H3Index)); BENCHMARK(cellToVertexes, 10000, { H3_EXPORT(cellToVertexes)(hex, vertexes); }); -BENCHMARK(cellToVertexesPent, 10000, - { H3_EXPORT(cellToVertexes)(pentagon, vertexes); }); +BENCHMARK(cellToVertexesPent, 10000, { + H3_EXPORT(cellToVertexes)(pentagon, vertexes); +}); BENCHMARK(cellToVertexesRing, 10000, { for (int i = 0; i < ring2Count; i++) { diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index 184a14690d..a90574e1f8 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -92,50 +92,47 @@ /** * Gets the highest bit of the H3 index. */ -#define H3_GET_HIGH_BIT(h3) \ - ((int)((((h3) & H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) +#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) /** * Sets the highest bit of the h3 to v. */ -#define H3_SET_HIGH_BIT(h3, v) \ - (h3) = (((h3) & H3_HIGH_BIT_MASK_NEGATIVE) | \ +#define H3_SET_HIGH_BIT(h3, v) \ + (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ (((uint64_t)(v)) << H3_MAX_OFFSET)) /** * Gets the integer mode of h3. */ -#define H3_GET_MODE(h3) ((int)((((h3) & H3_MODE_MASK) >> H3_MODE_OFFSET))) +#define H3_GET_MODE(h3) ((int)((((h3)&H3_MODE_MASK) >> H3_MODE_OFFSET))) /** * Sets the integer mode of h3 to v. */ #define H3_SET_MODE(h3, v) \ - (h3) = \ - (((h3) & H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) + (h3) = (((h3)&H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) /** * Gets the integer base cell of h3. */ -#define H3_GET_BASE_CELL(h3) ((int)((((h3) & H3_BC_MASK) >> H3_BC_OFFSET))) +#define H3_GET_BASE_CELL(h3) ((int)((((h3)&H3_BC_MASK) >> H3_BC_OFFSET))) /** * Sets the integer base cell of h3 to bc. */ #define H3_SET_BASE_CELL(h3, bc) \ - (h3) = (((h3) & H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) + (h3) = (((h3)&H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) /** * Gets the integer resolution of h3. */ -#define H3_GET_RESOLUTION(h3) ((int)((((h3) & H3_RES_MASK) >> H3_RES_OFFSET))) +#define H3_GET_RESOLUTION(h3) ((int)((((h3)&H3_RES_MASK) >> H3_RES_OFFSET))) /** * Sets the integer resolution of h3. */ #define H3_SET_RESOLUTION(h3, res) \ - (h3) = \ - (((h3) & H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) + (h3) = (((h3)&H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) /** * Gets the resolution res integer digit (0-7) of h3. @@ -148,15 +145,15 @@ * Sets a value in the reserved space. Setting to non-zero may produce invalid * indexes. */ -#define H3_SET_RESERVED_BITS(h3, v) \ - (h3) = (((h3) & H3_RESERVED_MASK_NEGATIVE) | \ +#define H3_SET_RESERVED_BITS(h3, v) \ + (h3) = (((h3)&H3_RESERVED_MASK_NEGATIVE) | \ (((uint64_t)(v)) << H3_RESERVED_OFFSET)) /** * Gets a value in the reserved space. Should always be zero for valid indexes. */ #define H3_GET_RESERVED_BITS(h3) \ - ((int)((((h3) & H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) + ((int)((((h3)&H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) /** * Sets the resolution res digit of h3 to the integer digit (0-7)