diff --git a/.ci/scripts/abi_check.sh b/.ci/scripts/abi_check.sh index e92c15dad86..cfd90f196b9 100755 --- a/.ci/scripts/abi_check.sh +++ b/.ci/scripts/abi_check.sh @@ -40,4 +40,72 @@ do # ignore a return value of 4 as it signifies a possibly compatible change if [ $retVal != 4 ]; then RETURN_CODE=$(($RETURN_CODE+$retVal)); fi done + +# Cross-flavor public-symbol drift check. +# +# abidiff above catches per-library ABI changes. When debug info is stripped +# (the DPC++ libraries in CI to keep memory usage in check), abidiff falls +# back to symbol addition/removal and misses layout/return-type breaks. As a +# cheap supplement, diff the set of newly-exported symbols between the host +# (_c) and DPC++ flavors of the same library: if a PR adds a public symbol +# to one flavor but not the other, the public surface has diverged and a +# reviewer should look at it. This works on .dynsym alone, so debug info is +# not required. +if ! command -v nm >/dev/null 2>&1; then + echo "::error:: nm not found (required for cross-flavor symbol drift check)" + exit 1 +fi + +new_syms () { + # symbols present in $2 but not in $1, filtered to defined + external. + # nm errors are not suppressed: an ABI gate that silently degrades to an + # empty symbol set would report "no drift" on tool failure. + set -o pipefail + comm -13 \ + <(nm -D --defined-only --extern-only "$1" | awk '{print $NF}' | sort -u) \ + <(nm -D --defined-only --extern-only "$2" | awk '{print $NF}' | sort -u) +} + +pairs=( + "libonedal.so:libonedal_dpc.so" + "libonedal_parameters.so:libonedal_parameters_dpc.so" +) + +for pair in "${pairs[@]}"; do + host_lib=${pair%:*} + dpc_lib=${pair#*:} + if [ ! -f "$main_release_dir/$host_lib" ] || [ ! -f "$release_dir/$host_lib" ] || \ + [ ! -f "$main_release_dir/$dpc_lib" ] || [ ! -f "$release_dir/$dpc_lib" ]; then + continue + fi + echo "======== cross-flavor symbol drift: ${host_lib} vs ${dpc_lib} ========" + if ! host_new=$(new_syms "$main_release_dir/$host_lib" "$release_dir/$host_lib"); then + echo "::error:: nm/comm failed for ${host_lib}" + RETURN_CODE=$((RETURN_CODE+1)) + continue + fi + if ! dpc_new=$(new_syms "$main_release_dir/$dpc_lib" "$release_dir/$dpc_lib"); then + echo "::error:: nm/comm failed for ${dpc_lib}" + RETURN_CODE=$((RETURN_CODE+1)) + continue + fi + # printf '%s' (not echo) so an empty set doesn't inject a spurious blank + # line into comm's input, which would produce false-positive drift. + only_in_dpc=$(comm -13 <(printf '%s' "$host_new") <(printf '%s' "$dpc_new")) + only_in_host=$(comm -23 <(printf '%s' "$host_new") <(printf '%s' "$dpc_new")) + if [ -n "$only_in_dpc" ]; then + echo "::error:: new public symbols in ${dpc_lib} with no counterpart in ${host_lib}:" + echo "$only_in_dpc" + RETURN_CODE=$((RETURN_CODE+1)) + fi + if [ -n "$only_in_host" ]; then + echo "::error:: new public symbols in ${host_lib} with no counterpart in ${dpc_lib}:" + echo "$only_in_host" + RETURN_CODE=$((RETURN_CODE+1)) + fi + if [ -z "$only_in_dpc" ] && [ -z "$only_in_host" ]; then + echo "no drift" + fi +done + exit ${RETURN_CODE} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b325b9f6f91..4ecba47eeca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,14 +67,17 @@ jobs: - name: Make daal run: | source /opt/intel/oneapi/setvars.sh - .ci/scripts/build.sh --compiler icx --optimizations ${{ matrix.ISA }} --target daal --jobs 20 + .ci/scripts/build.sh --compiler icx --optimizations ${{ matrix.ISA }} --target daal --debug symbols --jobs 20 rm -rf __work - name: Make onedal id: onedal-dbg run: | source /opt/intel/oneapi/setvars.sh - .ci/scripts/build.sh --compiler icx --optimizations ${{ matrix.ISA }} --target oneapi_c --jobs 20 + .ci/scripts/build.sh --compiler icx --optimizations ${{ matrix.ISA }} --target oneapi_c --debug symbols --jobs 20 rm -rf __work + # TODO: enable --debug symbols for oneapi_dpc once CI memory pressure is addressed + # (e.g. suppress device DWARF via -Xarch_device -g0 / -fsycl-device-debug=none, or larger runner). + # Currently omitted because DPC++ device DWARF blows up per-object size and icx peak RSS at -j20. .ci/scripts/build.sh --compiler icx --optimizations ${{ matrix.ISA }} --target oneapi_dpc --jobs 20 # clean up build directory due to space limitations rm -rf __work @@ -163,6 +166,10 @@ jobs: - name: Check ABI conformance run: | echo "Note: This check uses abidiff to verify ABI compliance of an SSE/AVX512 oneDAL build." + echo "Host libraries (libonedal_core, libonedal, libonedal_thread, libonedal_parameters) are built" + echo "with --debug symbols so abidiff can use DWARF for full function/variable sub-type checks." + echo "The DPC++ libraries (libonedal_dpc, libonedal_parameters_dpc) are built without debug info" + echo "to keep CI memory usage in check; abidiff falls back to ELF-symbol addition/removal on them." echo "It compares to the last completed main build, whose commit can be found in the 'Check Cache' step." echo "The ABI for other ISAs are assumed to match the SSE or AVX512 version, any ISA-specific exported " echo "information that is ISA unique must be manually checked. If no shared objects are found, then "