Skip to content

ci: run the full test suite on the arm64 probe #4

ci: run the full test suite on the arm64 probe

ci: run the full test suite on the arm64 probe #4

name: arm64 codegen probe
# Diagnostic-only. VectorMath.MultiplyWidening32 falls through to the portable `x * y` on arm64
# (Avx2/Sse2 are both unsupported there), and NEON has no 64-bit vector multiply, so the JIT has to
# synthesise one. The open question is HOW: a vectorised umull-based decomposition, or a scalar
# software fallback. dotnet/runtime#103555 fixed exactly this for x64 and says nothing about arm64.
# This dumps the real arm64 disassembly so the answer comes from the machine, not from reasoning.
on:
push:
branches: [ 'perf/**' ]
workflow_dispatch:
jobs:
probe:
runs-on: ubuntu-24.04-arm
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Host
run: |
uname -m
lscpu | sed -n '1,20p'
- name: Build
run: |
dotnet restore src/Base58Encoding.slnx
dotnet build src/Base58Encoding.slnx --configuration Release --no-restore
# DOTNET_JitDisasm works on the release runtime since .NET 8. TieredCompilation=0 skips tier-0 so
# the first call is already fully optimised. The filter must be NAMESPACE-QUALIFIED: a bare
# "VectorMath:*" matches nothing, because the JIT names the method
# "Base58Encoding.VectorMath:TensorDot". JitDisasmSummary is captured alongside so that, if the
# filter ever stops matching again, the log shows every method actually compiled.
# The codegen step below filters to *VectorMathTests* to keep the disassembly readable, so on its
# own it proves nothing about the rest of the library. The publish workflow runs the full suite on
# arm64, but only for master and PRs into it — never for perf/** branches. Run it here so an arm64
# lane-ordering bug cannot reach master unnoticed.
- name: Test (full suite, arm64)
run: |
dotnet run --project src/Base58Encoding.Tests/Base58Encoding.Tests.csproj \
--configuration Release --no-build
- name: Dump arm64 codegen for VectorMath
env:
DOTNET_JitDisasm: 'Base58Encoding.VectorMath:*'
DOTNET_JitDisasmSummary: '1'
DOTNET_TieredCompilation: '0'
run: |
dotnet run --project src/Base58Encoding.Tests/Base58Encoding.Tests.csproj \
--configuration Release --no-build -- -method "*VectorMathTests*" \
> arm64-codegen.txt 2>&1 || true
echo "----- captured $(wc -l < arm64-codegen.txt) lines -----"
echo "----- methods the JIT compiled from VectorMath -----"
grep -E 'JIT compiled Base58Encoding\.VectorMath' arm64-codegen.txt || echo "(none - were they inlined?)"
- name: Verdict
run: |
set -u
listings=$(grep -cE '^; Assembly listing for method' arm64-codegen.txt || true)
echo "assembly listings captured: $listings"
if [ "$listings" -eq 0 ]; then
echo "::error::No disassembly captured - the JitDisasm filter matched nothing, so the census below would be meaningless."
exit 1
fi
echo
echo "=== method headers ==="
grep -E '^; Assembly listing for method|^; Emitting|Total bytes of code' arm64-codegen.txt
echo
echo "=== instruction census (what the 64-bit multiply lowered to) ==="
for m in umull umull2 uzp1 uzp2 mul shl ushr usra add bl blr; do
printf '%-8s %s\n' "$m" "$(grep -cE "^[[:space:]]+$m( |$)" arm64-codegen.txt || true)"
done
echo
echo "=== calls out of the kernels (scalar software fallback would show here) ==="
grep -nE '^[[:space:]]+(bl|blr)( |$)' arm64-codegen.txt || echo "no calls - not scalarising"
echo
echo "=== every instruction in the two kernels ==="
sed -n '/^; Assembly listing for method Base58Encoding.VectorMath/,/^; Total bytes of code/p' arm64-codegen.txt
- name: Upload full dump
if: always()
uses: actions/upload-artifact@v7
with:
name: arm64-codegen
path: arm64-codegen.txt