Repository navigation
flexShrink runaway (~8e9) when the sole shrinkable item has an aspectRatio basis; FMA residual slips past the 1e-6f guard from #1974 #2038
Description
Sibling case to #1665 / #1783. The 1e-6f guard added in #1974 does not cover it: the residual here is 1.9e-6, and it comes from FMA contraction rather than ordinary accumulation error.
Reproduction
Four nodes, public C API, default config:
#include <yoga/Yoga.h>
#include <cstdio>
int main() {
YGNodeRef root = YGNodeNew();
YGNodeStyleSetWidth(root, 350);
YGNodeStyleSetHeight(root, 350);
YGNodeRef row = YGNodeNew();
YGNodeStyleSetFlexDirection(row, YGFlexDirectionRow);
YGNodeInsertChild(root, row, 0);
YGNodeRef box = YGNodeNew();
YGNodeStyleSetAspectRatio(box, 22.0f / 24.0f); // basis = 23 * 22/24 = 21.083334
YGNodeStyleSetFlexShrink(box, 3);
YGNodeInsertChild(row, box, 0);
YGNodeRef sibling = YGNodeNew(); // overflows the row; flexShrink stays 0
YGNodeStyleSetWidth(sibling, 600);
YGNodeStyleSetHeight(sibling, 23);
YGNodeInsertChild(row, sibling, 1);
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
printf("box %g x %g\n", YGNodeLayoutGetWidth(box), YGNodeLayoutGetHeight(box));
}clang++ -std=c++20 -O2 -I. repro.cpp $(find yoga -name '*.cpp') && ./a.out
| box | |
|---|---|
| expected | 0 x 0 (basis 21.08, line overflows by 271, shrinks to zero) |
| actual | 8290303488 x 9043966976 |
flexShrink 1 and 2 clamp to 0 correctly; 3, 6, 9, 11, 12, 19, 22, 999 all explode. Which values fail depends on the mantissa of flexShrink * flexBasis, so there is no safe non-zero value to pick.
Reproduces on main @ 667da3c (2026-10-13) and in the Yoga bundled with React Native 0.86.3. Apple clang, arm64. It does not reproduce with -ffp-contract=off, which is also why the website playground (wasm) cannot show it.
Mechanism
Instrumenting the three places totalFlexShrinkScaledFactors is touched (-O2, default -ffp-contract=on):
[calculateFlexLine] shrink=3 basis=21.083334 product=-63.25 total=-63.25
[distributeFreeSpaceFirstPass] freeze, boundMainSize=0 -> total=1.90734863e-06
[distributeFreeSpaceSecondPass] remainingFreeSpace=-250 total=1.9e-06 guard=0 -> childSize=8.29e+09
The exact product 3 * 21.083334f is 63.2500019, which is a rounding tie; stored as a float it rounds to even, 63.25. calculateFlexLine does total += -shrink * basis and ends up with -63.25 either way. distributeFreeSpaceFirstPass then does
flexLine.layout.totalFlexShrinkScaledFactors -=
(-currentLineChild->resolveFlexShrink() *
currentLineChild->getLayout().computedFlexBasis.unwrap());which the compiler contracts into a single fmadd, using the unrounded product: -63.25 + 63.2500019 = 2^-19 = 1.9e-6. That is just above the 1e-6f absolute guard in the second pass, so the remaining free space is divided by it.
With -ffp-contract=off the same line gives exactly 0 and the guard fires. Since the residual scales with flexShrink * flexBasis, no absolute epsilon is the right check.
Three preconditions, remove any one and it behaves: aspectRatio on the shrinking item (the basis has to be a non-representable product), flexDirection: row, and an overflowing sibling that is not itself shrinkable (with two shrinkable items the totals do not cancel to a residual). Note the third means the repro silently passes under useWebDefaults, where flexShrink defaults to 1.
Fix
#2039 rounds the product to float before the subtraction and replaces the epsilon with an exact count of shrinkable items left on the line, kept in lockstep with the total. The count is the part that stays correct under -ffp-contract=fast, where hoisting alone can still be contracted. Includes the 4-node repro above as a gtest; full suite passes.
Activity
Fix flex shrink sizes when the first pass freezes items
Treat a positive flex shrink total as float residual
Treat a positive flex shrink total as float residual
arkivanov commentedon Oct 20, 2026
The same issue with the Java wrapper. The proposed fix seems to solve the problem here as well.
Fix flex shrink explosion when the shrink total is a positive float r…
Fix flex shrink explosion when the shrink total is a positive float r…
Fix flex shrink explosion when the shrink total is a positive float r…