跪拜 Guibai
← All articles
Frontend · Android · Flutter

Dart AOT Gets Math Wrong: Debug Outputs 0.125, Release Outputs 2299087609772638200

By 恋猫de小郭 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Flutter release builds run under AOT, so any app that mixes numeric types from dynamic sources like JSON can silently crash or return corrupted numbers on ARM64. This is a concrete reliability trap for production Dart code.

Summary

Dart's AOT compiler can miscompile mathematical code that mixes int and double values. In a Dart VM JIT, `min(0.965, jsonDecode('{"value":0}'))` returns 0 and `0.035 + 0` gives 0.035. Under AOT, the same code triggers SIGSEGV on ARM64 because the compiler assumes the return value is a Double, treats the Smi-tagged integer zero as an object reference, and tries to read a float from address 0x7.

The second example is worse: `max(0, 0.25) * 0.5` computes 0.125 in JIT, but AOT returns 2299087609772638200.0. The root cause is that `min` and `max` are generic with `T extends num`, so arguments can be int or double. When the value comes from a JSON-decoded `Map` with static type `dynamic`, the compiler's `InlineMathMinMax` optimization can't guarantee both operands are doubles, but its ARM64 backend still emits code that unboxes the result as a Double. The result is a wrong machine-code path that reads a valid small integer as a pointer.

The bug also appears in Flutter 3.44.9 via register-argument passing, where a default 1.0 value is treated as an address. The issue is tracked in dart-lang/sdk#64878, but the exact compiler boundary that causes it remains unclear.

Takeaways
— AOT can SIGSEGV on `min(0.965, 0)` because the compiler treats an Smi-tagged integer zero as a Double object reference.
— AOT computes `max(0, 0.25) * 0.5` as 2299087609772638200.0 instead of 0.125.
— Dart's `min`/`max` are generic with `T extends num`; arguments are not automatically converted to double.
— The Dart compiler has an `InlineMathMinMax` optimization that only kicks in when both arguments are the same type (both double or both int).
— The bug is tracked as dart-lang/sdk#64878, and a similar register-passing crash occurred in Flutter 3.44.9.
Conclusions

The error is not a rounding issue but a type-unsafe machine-code path: the compiler assumes a value is a Double reference when it is actually a Smi integer.

AOT's aggressive optimization can produce silent numeric corruption, not just crashes, which is harder to detect in production.

Dynamic typing from `jsonDecode` combined with Dart's generic `num` creates a type-inference gap that the compiler doesn't close before emitting assembly.

Concepts & terms
SSA phi node
In static single assignment form, a phi node merges values arriving from different control-flow branches; each branch can produce a different type, so the phi's type may be imprecise until runtime.
Smi (small integer) tagging
Dart VM encodes small integers (Smi) in a tagged machine word, often with a low-bit tag; a Smi zero is represented as the actual number 0, not as a pointer to an object.
Unbox
A compiler instruction that extracts a primitive value (like a double) from a boxed object; when the object is actually an integer, unboxing as double reads the wrong memory location.
AOT vs JIT
Ahead-of-time compilation translates Dart to native machine code before running, while just-in-time compiles at runtime; AOT enables faster startup but can expose static type assumptions that JIT can correct dynamically.
From the discussion

The discussion turns on Dart's type design and the recurrence of debug/release divergences. One view holds that Dart's num design is ugly, implying the mixed int/double overloads are at fault. Another notes that debug/release mismatches have happened several times before, suggesting a pattern of AOT reliability problems.

— Dart's num type design is criticized as ugly, likely entangled with the mixed-type overloads that trigger the AOT miscompilation.
— Debug/release behavior differences are not a one-off; they have occurred repeatedly, pointing to a systemic AOT issue rather than an isolated bug.
Featured comments
茶树米

Dart's num design feels ugly.

卡米

It seems like this kind of thing—debug fine, release broken—has happened several times already.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗