[fix](be) Fix ARM64 correctness, alignment UB, and build compatibility - #66857
[fix](be) Fix ARM64 correctness, alignment UB, and build compatibility#66857u70b3 wants to merge 6 commits into
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
a7a87ab to
f55f0c8
Compare
49c43f0 to
80432a7
Compare
### What problem does this PR solve? Issue Number: None Related PR: apache#66857 Problem Summary: The ARM hardening changes used Bash 4 lowercase expansion in the third-party build, which breaks the Bash 3.2 shipped by supported macOS runners. The bit-packing alignment fix also left the whole unpack function excluded from undefined-behavior sanitization, so the new alignment regression test could not detect a reintroduced misaligned dereference. Use Bash 3-compatible case patterns and restore UBSan instrumentation now that all word reads use memcpy-based unaligned_load. ### Release note None ### Check List (For Author) - Test: Unit Test / Manual test - Bash syntax and extracted boolean case matrix - BE build hygiene and diff checks - Clang 19 alignment-sanitized BitPackingUnalignedTest (1/1 passed) - Behavior changed: No (restores supported build compatibility and sanitizer coverage) - Does this need documentation: No
### What problem does this PR solve? Issue Number: None Related PR: apache#66857 Problem Summary: The ARM hardening changes used Bash 4 lowercase expansion in the third-party build, which breaks the Bash 3.2 shipped by supported macOS runners. The bit-packing alignment fix also left the whole unpack function excluded from undefined-behavior sanitization, so the new alignment regression test could not detect a reintroduced misaligned dereference. Use Bash 3-compatible case patterns and restore UBSan instrumentation now that all word reads use memcpy-based unaligned_load. ### Release note None ### Check List (For Author) - Test: Unit Test / Manual test - Bash syntax and extracted boolean case matrix - BE build hygiene and diff checks - Clang 19 alignment-sanitized BitPackingUnalignedTest (1/1 passed) - Behavior changed: No (restores supported build compatibility and sanitizer coverage) - Does this need documentation: No
39280bd to
62880e9
Compare
|
run buildall |
|
/review |
|
Hi @gavinchou @yiguolei @Gabriel39 @morningman — gentle ping for review when you have a moment. This PR fixes several aarch64 (ARM64) correctness bugs reproduced on real ARM hardware (128-core server): a non-atomic CAS in the crash-handler election, AArch32 asm in the startup NEON check, a hardcoded x86-64 bfd target, and a SIMD popcount path silently compiled out on aarch64. It also adds zero-cost alignment-UB / memory-ordering hardening and the build fixes needed to compile master on a current aarch64 toolchain. Every fix carries a regression test, and buildall CI is running now. The PR spans a few independent fault domains — I'm happy to split it into per-theme PRs if that makes review and backporting easier. Thanks! |
|
Backport analysis for reviewers/committers: these bugs exist on all active release lines, but branch-4.0 and earlier still use the old Could a committer please add the |
TPC-H: Total hot run time: 17022 ms |
TPC-DS: Total hot run time: 84013 ms |
ClickBench: Total hot run time: 14.91 s |
|
run buildall |
- SignalHandlerTest: 128-thread race on the CAS used by the crash-handler thread election. On aarch64 (where the fallback chain ends in a naive non-atomic read-check-write) 2-13 of 128 threads win the election; exactly one must win. - NGramBloomFilter / BitPacking / BitmapIntersect / HashUtil unaligned tests: exercise the code paths with deliberately misaligned buffers (offset 1..7) so the memcpy-based hardening is regression-guarded and UBSan-ready. - ColumnsCommonTest: pin the signed '>' 0 semantics of count_bytes_in_filter (bytes 128..255 must not be counted) so the scalar and SIMD paths always agree.
Real bugs: - common/signal_handler.h: the CAS used to elect the single crash-dumping thread fell back to a non-atomic read-check-write on aarch64 (HAVE___SYNC_VAL_COMPARE_AND_SWAP is never defined by this CMake build and the inline-asm branch is x86-only). On a 128-core ARM server, 2-13 of 128 racing threads won the election, corrupting crash dumps. Use __atomic_compare_exchange_n which exists on all supported arches. - service/doris_main.cpp: the startup NEON self-check used AArch32 asm (vadd.i32 q8,...) which no aarch64 assembler accepts once __ARM_NEON__ is defined (Clang targets); pick the spelling per architecture. - util/bfd_parser.cpp: bfd_set_default_target hardcoded elf64-x86-64, which fails on aarch64 (the x86 backend is not bundled there) and logged a spurious error on every BE start; select per-arch target. - core/column/columns_common.cpp: '|| &&' precedence typo silently compiled out the SIMD path of count_bytes_in_filter on aarch64. - thirdparty/build-thirdparty.sh: [[ USE_AVX2 -eq 0 ]] is true for 'ON' (bash arithmetic), silently disabling croaring AVX2 for users who export USE_AVX2=ON; use a string compare. Misaligned-access UB hardening (zero-cost unaligned_load/memcpy; aarch64 scalar loads tolerate misalignment but the casts are UB and break UBSan): - util/bit_packing.inline.h UnpackValue (parquet/RLE decode hot path) - util/bitmap_intersect.h serialize/deserialize helpers - storage/types.h BaseFieldTypeTraits::get_cpp_type_value (packed rows) - util/hash_util.hpp crc_hash/crc_hash64/murmur_hash2_64 - ngram_bloom_filter.cpp init (also fixes reserve-then-index misuse) Weak-memory-ordering hardening for aarch64: - io/cache/block_file_cache_profile: use atomic_shared_ptr for lock-free stats access (DCL on a plain shared_ptr can observe a torn or half-constructed object) - storage/olap_common.h VersionWithTime: atomic update_ts and release/acquire CAS so readers never see new version + stale ts - bucketed_aggregation_source_operator: acquire load for the cross-thread merge-target index (was relying on data-dependency ordering) - util/histogram.cpp: _mm_pause backoff in CAS retry loops
- bitmap_value.h: suppress -Wshadow for BitmapDataType enum constants (they collide with protobuf-exported doris::BITMAP; clang >= 17 turns it into an -Werror failure) - be/CMakeLists.txt: keep -Wshadow non-fatal on clang, and -Wno-deprecated-declarations for BE_TEST (libstdc++ >= 12 marks std::get_temporary_buffer deprecated, tripping existing tests) - parquet_column_convert.h: std::powf -> std::pow (libstdc++ <cmath> does not declare powf in namespace std) - function_string_misc.cpp: std::format -> fmt::format (libstdc++ 12 has no <format>; fmt is the project convention) - be_thread_stack_action.cpp: append_frame is only used on the x86_64 unwind path; mark [[maybe_unused]] so aarch64 -Werror builds pass - glibc-compatibility: add resolv_shim.c forwarding __res_nsearch to res_nsearch — glibc >= 2.34 exports the former only as a non-default compat version, breaking the link against the prebuilt krb5 archive
…consistency Review round for the aarch64 hardening PR: - olap_common.h: store update_ts BEFORE the release CAS that publishes a new version, so a reader can never observe "new version + stale timestamp" (the previous order left an interleaving window on any arch); document the exact invariant instead of overclaiming; add VersionWithTimeTest concurrency regression test. - ngram_bloom_filter: init() copied words*8 rounded-up bytes from the caller buffer, over-reading up to 7 bytes for bf sizes not divisible by 8 (e.g. 65535). Copy exactly size bytes and keep the tail zeroed so contains() matches query-side filters; add exact-size ASan guard test (65/67/100/511/65535). The old "reserve()-then-index" root-cause story was wrong: the constructor already sized the vector. - block_file_cache_profile: replace the atomic_shared_ptr (not lock-free: libstdc++ lock pool / legacy free-function lock table) with an AtomicStatistics value member and register the metrics hook in the constructor. The lazy-init publication race it guarded was unreachable because the singleton is fully constructed before publication. - build-thirdparty.sh: accept CMake boolean spellings for USE_AVX2 (0/OFF/FALSE/NO, case-insensitive) so croaring agrees with BE's CMake when users set e.g. USE_AVX2=OFF; unknown values warn and keep default. - glibc-compatibility: resolv_shim.c was compiled twice (globbed into the archive AND listed in the explicit OBJECT lib); REMOVE_ITEM it from the globbed sources. - signal_handler.h / columns_common.cpp: fix stale and misleading comments (the naive CAS fallback is gone; the aarch64 SIMD guard was not "parenthesized" - the __POPCNT__ gate was dropped). - tests: unaligned-buffer coverage for crc_hash and crc_hash64 (only murmur_hash2_64 had it); VecDateTimeValue misaligned round-trip for bitmap_intersect (the int32 test comment overclaimed); correct the int32 test comment. - snii/encoding/crc32c.cpp: define SNII_CRC32C_X86 as 0 on non-x86 so the BE_TEST-only TU compiles with -Wundef -Werror on aarch64. Pre-existing break from apache#66809 that blocked linking doris_be_test, unrelated to the review edits. Verified on aarch64 (clang 19.1.7, -march=armv8-a+crc): full ninja doris_be_test build clean; 16/16 tests pass across VersionWithTimeTest, NGramBloomFilterTest, HashUtilUnalignedTest, BitmapIntersectTest, SignalHandlerTest, ColumnsCommonTest, BitPackingUnalignedTest.
### What problem does this PR solve? Issue Number: None Related PR: apache#66857 Problem Summary: The ARM hardening changes used Bash 4 lowercase expansion in the third-party build, which breaks the Bash 3.2 shipped by supported macOS runners. The bit-packing alignment fix also left the whole unpack function excluded from undefined-behavior sanitization, so the new alignment regression test could not detect a reintroduced misaligned dereference. Use Bash 3-compatible case patterns and restore UBSan instrumentation now that all word reads use memcpy-based unaligned_load. ### Release note None ### Check List (For Author) - Test: Unit Test / Manual test - Bash syntax and extracted boolean case matrix - BE build hygiene and diff checks - Clang 19 alignment-sanitized BitPackingUnalignedTest (1/1 passed) - Behavior changed: No (restores supported build compatibility and sanitizer coverage) - Does this need documentation: No
…rsion On glibc < 2.34 (e.g. the AlmaLinux 8 / glibc 2.28 CI build image), <resolv.h> #defines res_nsearch as __res_nsearch, so the forwarder body called itself and clang -Werror -Winfinite-recursion broke the BE UT and COMPILE checks. The shim is only needed on glibc >= 2.34 anyway (older glibc still exports __res_nsearch as a default-versioned symbol), so gate it with __GLIBC_PREREQ(2, 34) and keep the translation unit non-empty for -Wpedantic.
70d268f to
0204934
Compare
|
run buildall |
|
/review |
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
What problem does this PR solve?
Problem Summary:
Systematic analysis of the BE codebase for aarch64 (ARM64) hazards — x86-only code paths, misaligned-access UB, and weak-memory-model races — done on a 128-core ARM server (aarch64, Ubuntu 22.04). Several real bugs were found and reproduced, plus a class of latent UB that is fragile on ARM. This PR fixes the real bugs, hardens the latent ones with zero-cost changes, and adds regression tests. A final commit fixes what prevents master from compiling at all on a current aarch64 toolchain.
Real bugs (reproduced on aarch64):
common/signal_handler.h— the crash-handler election CAS is non-atomic on aarch64. The fallback chain requiresHAVE___SYNC_VAL_COMPARE_AND_SWAP(never defined by this CMake build) or x86 inline asm, so aarch64 lands in a naive read-check-write that "has a race condition" (per its own comment).FailureSignalHandler()uses it to elect the single thread that dumps crash state. Reproduced with a 128-thread gtest: 2–13 threads won the election per run (should be exactly 1), which means concurrent crashers corrupt each other's dumps or crash again inside the handler. Fixed with__atomic_compare_exchange_n(available on every supported arch).service/doris_main.cpp— AArch32 asm in the startup NEON check.vadd.i32 q8,q8,q8does not assemble on AArch64 once__ARM_NEON__is defined (Clang defines it on some ARM targets); reproduced:unknown mnemonic 'vadd.i32'. Now picksadd v8.4s,...on__aarch64__and keeps the AArch32 spelling on__arm__.util/bfd_parser.cpp— hardcodedbfd_set_default_target("elf64-x86-64")fails on aarch64 (verified: the x86 backend is not bundled in aarch64 libbfd), logging a spurious error at every BE start. Selects the target per architecture.core/column/columns_common.cpp— the SIMD path ofcount_bytes_in_filterwas silently compiled out on aarch64. The guard#if defined(__SSE2__) || defined(__aarch64__) && defined(__POPCNT__)already parses as__SSE2__ || (__aarch64__ && __POPCNT__)(&&binds tighter), and ARM toolchains never define__POPCNT__(sse2neon does not define__SSE2__either), so aarch64 always took the scalar loop. Dropped the__POPCNT__gate so the block is enabled unconditionally on__aarch64__(same SSE2 intrinsics via sse2neon; results verified bit-identical by ColumnsCommonTest).thirdparty/build-thirdparty.sh—[[ "${USE_AVX2}" -eq 0 ]]is true forUSE_AVX2=ON(bash arithmetic evaluates "ON" to 0), silently disabling croaring AVX2 for users following the script's own help text. Now normalized case-insensitively:0/OFF/FALSE/NOdisable,1/ON/TRUE/YESor empty/unset keep enabled (consistent with the CMake boolean semantics BE sees via-DUSE_AVX2=...), unknown values warn and keep the default.Misaligned-access UB hardening (aarch64 scalar loads tolerate misalignment so nothing crashes today, but these are C++ UB, trip UBSan, and can miscompile under aggressive optimization;
unaligned_load/memcpycompile to the same single load instruction — zero cost):util/bit_packing.inline.h(parquet/RLE decode hot path),util/bitmap_intersect.h(serialize/deserialize),storage/types.h(get_cpp_type_valueon packed rows),util/hash_util.hpp(crc_hash/crc_hash64/murmur_hash2_64),ngram_bloom_filter.cpp(init— also fixes a rounded-up tail over-read: it copiedwords * sizeof(uint64_t)bytes from asize-byte buffer, i.e. up to 7 bytes past the end for bf sizes not divisible by 8 such as 65535; it now copies exactlysizebytes and keeps the last word's tail zeroed socontains()still matches query-side filters).Weak-memory-model hardening (aarch64 is weakly ordered):
io/cache/block_file_cache_profile: the statistics counters are now a plainAtomicStatisticsvalue member with the metrics hook registered in the constructor. The previousatomic_shared_ptrload on the hot read path was not lock-free (libstdc++ implementsstd::atomic<shared_ptr>with an internal lock pool; the project's libc++ fallback uses the legacy lock-table free functions), and the lazy-init publication race it guarded was unreachable — the singleton is fully constructed beforeinstance()publishes it.storage/olap_common.hVersionWithTime:update_tsis atomic and is stored before the release CAS that publishes a new version. A reader that acquire-loadsversionand thenupdate_tsis therefore guaranteed a timestamp at least as new as the one stored for that version's publication; "new version + stale timestamp" cannot happen (the previous store-after-CAS order left that interleaving window open on every architecture, x86 included). The residual "old version + newer ts" combination only biases compaction toward the conservative max retention count. Covered by a deterministic multi-threaded regression test.bucketed_aggregation_source_operator: relaxed load of the merge-target index (whose value is then dereferenced cross-thread) → acquire, matching the sibling use in the same file.util/histogram.cpp: CAS retry loops get_mm_pause(maps toisbvia sse2neon on aarch64) to avoid LL/SC contention storms on many-core ARM.Build fixes (separate commit — master does not compile on aarch64 Ubuntu 22.04 with a current distro toolchain; needed to build/test any of the above):
clang-19 compat:
-Wshadowkept non-fatal (newer clang flags namespace shadowing by protobuf-generated enums),std::powf→std::pow,std::format→fmt::format,[[maybe_unused]]for a function only used on the x86_64 unwind path, UT-Wno-deprecated-declarations(libstdc++ ≥ 12), a__res_nsearch→res_nsearchforwarder in glibc-compatibility (glibc ≥ 2.34 exports it only as a non-default compat version, which breaks linking the prebuilt krb5 archive), and#define SNII_CRC32C_X86 0on non-x86 insnii/encoding/crc32c.cpp(the BE_TEST-only TU used the macro in#ifwithout defining it off-x86, so-Wundef -Werrorbroke the aarch64doris_be_testlink).How verified (TDD on a 128-core aarch64 server)
-fsanitize=alignment); the ngram exact-size test (bf sizes 65/67/100/511/65535, zero-slack buffers) is the ASan guard for the tail over-read.VersionWithTimeTest(4 readers × 20k version publications) deterministically passes with the fixed publication order and has a real failure window under the old order.SignalHandlerTest,NGramBloomFilterTest,BitPackingUnalignedTest,BitmapIntersectTest(incl.VecDateTimeValuekeys),ColumnsCommonTest,HashUtilUnalignedTest(murmur + crc + crc64),VersionWithTimeTest: 16/16 pass on aarch64 (clang 19.1.7,-march=armv8-a+crc), fulldoris_be_testlink included.-Werror), single-node cluster smoke test (create/insert/filter/aggregate/md5) verified.Known follow-ups (out of scope for this PR)
-Wno-error=shadowexemption: existing warnings (mostly protobuf-generated enum collisions) are numerous and deserve a scoped cleanup PR.ASAN_UTbuild so the over-read guard actively trips on regression (functional semantics already verified in the normal build).bucketed_aggregationacquire load and histogram_mm_pausehave no dedicated tests — memory-ordering and spin-hint behavior are not practically unit-testable; the comments there are the guard.Release note
Fix several aarch64 (ARM64) correctness and build issues in BE, including a non-atomic CAS in the crash signal handler, and harden hot paths against misaligned-access UB and weak-memory-ordering hazards.
Check List (For Author)
Test
-march=armv8-a+crc), started a single-node cluster, ran create/insert/select smoke SQL successfully. Unit tests:run-be-ut.sh --run --filter='SignalHandlerTest.*:NGramBloomFilterTest.*:BitPackingUnalignedTest.*:BitmapIntersectTest.*:ColumnsCommonTest.*:HashUtilUnalignedTest.*:VersionWithTimeTest.*'(16/16 pass).Behavior changed:
count_bytes_in_filteris enabled (same results, faster), the bfd default target is selected per architecture (spurious BE-start error log on aarch64 is gone), and croaring now honors the documentedUSE_AVX2switch consistently with BE's CMake.Does this need documentation?