From cdd3f15cebe12154cc0087361a4f8b2ebd6c7d46 Mon Sep 17 00:00:00 2001 From: "jac-made[bot]" <350467601+jac-made[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:51:09 +0100 Subject: [PATCH] warp: make the curve fit independent of the CPU NumPy computes ** with a different AVX-512 code path on some CPUs. The last-bit differences changed one curve fit (Xi, Regular, up to 2 font units), so a fresh build on GitHub runners without AVX-512 differed from the committed fonts and the 'fonts are current' check failed about half of the runs, with or without a dependency bump. Cubic Bezier terms are now written as products. The fonts do not change. A test compares the result with the AVX-512 code paths switched off. Co-authored-by: Ricardo Frantz <4220623+ricardofrantz@users.noreply.github.com> --- warp/loom/outline.py | 8 +++++--- warp/tests/test_warp.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/warp/loom/outline.py b/warp/loom/outline.py index 5dfcc35..8e47c9f 100644 --- a/warp/loom/outline.py +++ b/warp/loom/outline.py @@ -174,7 +174,9 @@ def corners(a: np.ndarray) -> list[int]: def _bez(ctrl, t): t = np.asarray(t)[:, None] m = 1 - t - return m ** 3 * ctrl[0] + 3 * m * m * t * ctrl[1] + 3 * m * t * t * ctrl[2] + t ** 3 * ctrl[3] + # Products, not ** 3: NumPy computes power with a different (AVX-512) code + # path on some CPUs, and its last-bit differences change the curve fit. + return m * m * m * ctrl[0] + 3 * m * m * t * ctrl[1] + 3 * m * t * t * ctrl[2] + t * t * t * ctrl[3] def _bez_d1(ctrl, t): @@ -201,8 +203,8 @@ def _unit(v): def _generate(pts, u, t1, t2): p0, p3 = pts[0], pts[-1] - A1 = t1[None, :] * (3 * (1 - u) ** 2 * u)[:, None] - A2 = t2[None, :] * (3 * (1 - u) * u ** 2)[:, None] + A1 = t1[None, :] * (3 * (1 - u) * (1 - u) * u)[:, None] + A2 = t2[None, :] * (3 * (1 - u) * u * u)[:, None] C00 = (A1 * A1).sum() C01 = (A1 * A2).sum() C11 = (A2 * A2).sum() diff --git a/warp/tests/test_warp.py b/warp/tests/test_warp.py index c570444..a717beb 100644 --- a/warp/tests/test_warp.py +++ b/warp/tests/test_warp.py @@ -668,3 +668,31 @@ def test_build_is_deterministic(): assert [[(p.x, p.y, p.segmentType) for p in c] for c in ga.contours] == \ [[(p.x, p.y, p.segmentType) for p in c] for c in gb.contours] assert a.kerning == b.kerning and a.features.text == b.features.text + + +def test_curve_fit_does_not_depend_on_cpu_features(): + """NumPy uses other code (AVX-512) for some functions on some CPUs. The + fitted outlines, and so the fonts, must not change with it: the CI + machines differ, and the committed fonts are checked against a fresh build.""" + import subprocess + import sys + code = ( + "import numpy as np\n" + "from loom import outline as O\n" + "t = np.random.default_rng(3).random(4000)\n" + "ctrl = np.array([[0, 0], [37.3, 211.9], [402.1, 98.7], [455.5, 12.4]])\n" + "pts = O._bez(ctrl, np.linspace(0, 1, 60))\n" + "u = O._chord_params(pts)\n" + "fit = O._generate(pts, u, O._unit(ctrl[1] - ctrl[0]), O._unit(ctrl[2] - ctrl[3]))\n" + "print(O._bez(ctrl, t).tobytes().hex(), fit.tobytes().hex())\n" + ) + + def run(disable): + env = {k: v for k, v in __import__("os").environ.items() if k != "NPY_DISABLE_CPU_FEATURES"} + if disable: + env["NPY_DISABLE_CPU_FEATURES"] = disable + r = subprocess.run([sys.executable, "-c", code], cwd=WARP, env=env, capture_output=True, text=True) + assert r.returncode == 0, r.stderr + return r.stdout + + assert run("") == run("X86_V4 AVX512_ICL AVX512_SKX AVX512F")