Skip to content

Fix Difference hash seed widening on 32-bit (UInt == UInt32) - #688

Draft
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix/difference-hash-32bit
Draft

ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix/difference-hash-32bit

Conversation

@ChrisRackauckas-Claude

@ChrisRackauckas-Claude ChrisRackauckas-Claude commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Please ignore until reviewed by @ChrisRackauckas.

Summary

Base.hash(D::Difference, u::UInt) (src/difference.jl:41) xored the native-width seed
u against a bare literal, 0x055640d6d952f101, which does not fit in UInt32. On
64-bit builds UInt == UInt64 so the literal fits and nothing goes wrong, but on 32-bit
builds (UInt == UInt32) the xor silently widens the result to UInt64. The next
line then calls hash(D.t, ::UInt64) on a SymbolicUtils term, but SymbolicUtils's
hashconsing (hash_bsimpl) is defined for the platform's native UInt — UInt32 here —
so there is no matching method and it throws a MethodError.

Fix: reduce the literal with % UInt so the xor result stays in the caller's native
width on every platform:

Base.hash(D::Difference, u::UInt) = hash(D.dt, hash(D.t, xor(u, 0x055640d6d952f101 % UInt)))

This is the bug behind the master Core 32-bit CI failure in
https://github.com/SciML/DataDrivenDiffEq.jl/actions/runs/35188714123 (job
Core (julia 1, ubuntu-latest, x86), two failures in test/Core/implicit_basis.jl).
It is unrelated to #680 (which drops the x86 CI lane over a BFloat16s/NNlib
LLVM-on-i686 build error) — BFloat16s and NNlib both installed and precompiled cleanly
in that run; this failure is a genuine DataDrivenDiffEq hashing bug in test content,
not a dependency build problem.

Verification

All of this was run against real 32-bit Julia (juliaup add 1~x86, native i686 on this
x86_64 host, matching CI's julia-1.13.0+0.x86) in addition to the normal 64-bit build.

1. Added test — test/Core/implicit_basis.jl, right after the Difference that
triggers the bug:

d = Difference(get_iv(basis), dt = 1.0)
@test typeof(hash(d, UInt(1))) === UInt

Red (before the fix, on x86 Julia 1.13.0) — GROUP=Core julia +1~x86 --project -e 'using Pkg; Pkg.test()':

Solve implicit result: Error During Test at .../test/Core/implicit_basis.jl:62
  Test threw exception
  Expression: typeof(hash(d, UInt(1))) === UInt
  MethodError: no method matching hash(::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymbolicUtils.SymReal}, ::UInt64)
  ...
  Stacktrace:
   [1] hash(D::DataDrivenDiffEq.Difference, u::UInt32)
     @ DataDrivenDiffEq .../src/difference.jl:41
...
Test Summary:           | Pass  Error  Total   Time
Implicit Basis          |   12      3     15  25.0s

(The original two assertions at what are now lines 63-64 fail with the identical
MethodError seen in the CI run linked above.)

Green (after the fix, same x86 Julia 1.13.0) — same command:

Test Summary:  | Pass  Total   Time
Implicit Basis |   15     15  18.7s

2. GROUP=Core once on the default (64-bit) Julia, with the fix applied:

JULIA_DEPOT_PATH="<depot>:" GROUP=Core julia --project -e 'using Pkg; Pkg.test()'
...
Test Summary:      | Pass  Total  Time
DataDrivenSolution |   22     22  5.1s
Test Summary: | Pass  Total   Time
Utilities     |   35     35  35.1s
...
     Testing DataDrivenDiffEq tests passed

3. Runic / typos on the changed files, both clean:

julia --project=@runic -e 'using Runic; exit(Runic.main(ARGS))' -- --check src/difference.jl test/Core/implicit_basis.jl
# exit 0, no diff
typos src/difference.jl test/Core/implicit_basis.jl
# exit 0, no findings

What this does not fix

The x86 GROUP=Core run (both red and green) also hits a separate, pre-existing,
unrelated 32-bit failure in test/Core/utils.jl (Optimal Shrinkage):
MethodError: no method matching optimal_svht(::Int32, ::Int32) — optimal_svht is only
defined for Int64 arguments (src/utils/utils.jl:6). That is a different 32-bit bug and
out of scope for this PR; not touched here.

Risk assessment

  • Risk: low
  • Blast radius: one hash method on an internal Difference operator type used only
    for discrete-time difference equations; no public API signature changes, result values
    for hash on 64-bit builds are unchanged (literal already fits UInt64 there).
  • Evidence: failing-before/passing-after on real 32-bit Julia 1.13.0 shown above;
    implicit_basis.jl green on x86 after the fix; full GROUP=Core green on x64; on x86, test/Core/utils.jl
    still fails on the separate optimal_svht(::Int32, ::Int32) bug described above; Runic/typos clean.
  • Independent review: Cursor Agent (model auto) rated it low risk, high confidence, verdict MERGE. It ran the seed-width probe and old-vs-new hash on real x64 and x86 Julia 1.13.0: on x64 the hashes are identical, and on x86 the old one throws while the new one returns UInt32. It noted that the new test only discriminates on 32-bit CI, and caught the x86 wording fixed above. Cursor auto is a first-pass reviewer, not a top reviewer.
  • Merge: needs human review — restores the 32-bit CI lane's actual test content
    (distinct from the unrelated dependency-build issue Remove x86 CI lane (BFloat16s i686) #680 addresses)

🤖 Generated with Claude Code (model: claude-sonnet-5-5)

https://claude.ai/code/session_01QgnmpexRwud38wrs3XvBfV

Base.hash(::Difference, ::UInt) xored the native-width seed `u`
against a bare literal (0x055640d6d952f101), which only fits
UInt64. On 32-bit builds, where UInt == UInt32, this silently
widens the xor result to UInt64, so the subsequent
hash(D.t, ::UInt64) call on the SymbolicUtils term has no matching
method (SymbolicUtils's hashconsing expects the platform's native
UInt). Reducing the literal with `% UInt` keeps the result in the
caller's native width on every platform.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Agent-Harness: Claude Code
Agent-Model: claude-sonnet-5-5
Agent-Session: subagent of the qa-hygiene head session on amdci2

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants