Skip to content

Case 112: LP64 โ†’ ILP64 Integer-Model Switch (oneMKL MKL_INT 32โ†’64)

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux
Flags ABI break
Detected ChangeKinds integer_model_changed
Source files examples/case112_lp64_ilp64/

Category: Numerical-Library ABI Hazard | Verdict: ๐Ÿ”ด BREAKING

Verdict and consumer impact

v1 is the LP64 interface: MKL_INT is int (32-bit). v2 is the ILP64 interface: MKL_INT is long (64-bit). Every public entry point that takes a dimension, stride, or count โ€” and every function that returns one โ€” flips its integer width at the same time. The function names are unchanged (extern "C"), so a consumer linked against the LP64 build resolves the ILP64 symbols at load time but passes/reads integers with the wrong width: array indices and strides are silently truncated or misinterpreted. This is the highest-value numerical-library ABI hazard โ€” oneMKL ships both interfaces and they are not interchangeable.

Old/new diff

v1.h v2.h
typedef int MKL_INT; typedef long MKL_INT;

abicheck command

gcc -shared -fPIC -g v1.c -o libv1.so
gcc -shared -fPIC -g v2.c -o libv2.so
abicheck compare libv1.so libv2.so

Expected abicheck finding

Verdict: BREAKING (exit 4)

- typedef_base_changed: Typedef base type changed: MKL_INT (int -> long int)
  > Underlying type changed; old code using the typedef operates on wrong
    representation.
  Affected symbols: cblas_isamax, cblas_saxpy, cblas_sscal, mkl_get_max_threads
- integer_model_changed: Integer model changed (LP64 -> ILP64 (32-bit -> 64-bit)):
  integer typedef(s) resized: MKL_INT (int -> long int).
  > A large fraction of public integer parameters/returns flipped width
    together, or a public integer typedef changed its underlying size โ€”
    the signature of an LP64<->ILP64 model switch.

Minimum evidence

min_evidence: L1 โ€” DWARF alone carries the typedef's underlying base type (MKL_INT pointing at int in v1, long int in v2) and every public function's parameter/return types, which is enough for typedef_base_changed and, once enough public integer slots move together, integer_model_changed. No public headers are needed.

Why abicheck catches it

integer_model_changed fires because a large fraction of public integer parameters/returns flip width together and the MKL_INT typedef itself changes its underlying size (int โ†’ long int) โ€” DWARF's DW_TAG_typedef/DW_AT_type chain records both the typedef and each function's actual parameter types, so abicheck detects the correlated, library-wide width flip and reports it as one grouped diagnostic (backed by the individual typedef_base_changed finding) rather than leaving it as N unrelated per-function signature changes.

Runtime failure demonstration

Severity: CRITICAL (width-dependent โ€” silent for small positive values, crashes for negative/large ones).

The shipped app.c calls cblas_isamax/mkl_get_max_threads with small positive values; on x86-64 SysV, writing a 32-bit register (as the LP64 caller does) always zero-extends the upper 32 bits, so a small positive 32-bit int happens to read back correctly as a 64-bit long โ€” the app's exit code doesn't change across the swap for this specific fixture:

gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g app.c -L. -lv1 -Wl,-rpath,. -I. -o app
./app
# โ†’ exit 1  (cblas_isamax=0 + mkl_get_max_threads=1)

gcc -shared -fPIC -g v2.c -o libv1.so   # swap, no recompile
./app
# โ†’ exit 1  (same โ€” no visible symptom for this specific small-positive input)

A negative stride makes the same width mismatch fatal, since a negative 32-bit int written to a register does not sign-extend the upper 32 bits โ€” the ILP64 callee reads a huge positive long instead:

# a small standalone consumer calling cblas_sscal(n, 2.0f, x, incx=-1)
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g runtime_demo.c -L. -lv1 -Wl,-rpath,. -I. -o demo
./demo
# โ†’ ok, x[0]=2.000000   (exit 0)

gcc -shared -fPIC -g v2.c -o libv1.so   # swap, no recompile
./demo
# โ†’ Segmentation fault (exit 139)

Why CRITICAL: incx=-1 is written as a 32-bit register value by the LP64 caller; the ILP64 callee reads the same register as a 64-bit long with its upper bits zero rather than sign-extended, turning -1 into a huge positive stride and walking x[] far out of bounds.

Safe redesign

Never silently swap the integer model of a shipped interface. Ship LP64 and ILP64 as distinctly named libraries/symbol sets (as oneMKL actually does โ€” libmkl_intel_lp64 vs libmkl_intel_ilp64), or gate the choice at compile time via a distinct header/macro so a consumer's build fails loudly instead of linking against the wrong width silently.

Real-world example: oneMKL, OpenBLAS, and other BLAS/LAPACK distributions ship parallel LP64/ILP64 builds for exactly this reason โ€” mixing them (e.g. picking up the wrong .so at link or load time) is a well-known, hard-to-diagnose class of numerical-library bug reports.

Cross-tool comparison

abidw --out-file v1.xml libv1.so
abidw --out-file v2.xml libv2.so
abidiff v1.xml v2.xml

Source files

  • CMakeLists.txt
  • app.c
  • v1.c
  • v1.h
  • v2.c
  • v2.h

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.