Summary
Since the macOS Tahoe 26.5.1 update (June 2026; still present on 26.5.2), Apple's OpenCL runtime compiler miscompiles FluidX3D kernels on Apple Silicon in two mirrored ways:
- With the default build flags: if
SUBGRID and VOLUME_FORCE are both enabled, the body force silently vanishes — fluid never accelerates, velocity stays frozen at the half-step forcing offset (~0.5*f). No error, no NaN: simulations simply run without gravity/force. Without SUBGRID the force works correctly.
- With
-cl-opt-disable (the natural workaround for bug 1): moving boundaries die instead — a moving solid (MOVING_BOUNDARIES + update_moving_boundaries()) transfers no momentum to the fluid. With SUBGRID enabled the response is a silent ~zero; without SUBGRID the velocity field is corrupted to a uniform unphysical value (exactly −1/√3, deterministic).
We found no set of build flags on macOS 26.5.x where volume force and moving boundaries both work while SUBGRID is enabled. Everything worked on the same machine before the 26.5.1 OS update, the identical source tree passes equivalent tests on Linux + NVIDIA (driver 550.127.08) with both flag sets, and the bug reproduces on completely unmodified master — so this is an Apple compiler regression, not a FluidX3D bug. Filing it mainly as a heads-up for macOS users (silently wrong physics is the worst failure mode), with a usable workaround below.
System
- MacBook Pro, Apple M3 Pro (OpenCL device
Apple M3 Pro, driver string 1.2 1.0 (macOS), OpenCL C 1.2)
- macOS Tahoe 26.5.1 and 26.5.2 (both affected; last known-good state was before the 26.5.1 update, installed 2026-06-19)
- Host compiler: Apple clang 15.0.0 (clang-1500.3.9.4)
- FluidX3D current master (
fbcf16f, 2026-07-11); also reproduces on 99a63e5 (2026-05-25), with and without the b4481ae unroll-hint removal — makes no difference
Minimal repro
defines.hpp, starting from the shipped defaults: comment out BENCHMARK, enable VOLUME_FORCE, MOVING_BOUNDARIES, SUBGRID. Keep D3Q19, SRT, FP16S as shipped.
Replace main_setup() in src/setup.cpp with:
#include "setup.hpp"
// TEST 1 (VOLUME_FORCE): uniform body force fx on an all-periodic box of resting fluid.
// Correct: mean u.x grows ~ t*fx (~0.01 after 1000 steps). Broken: stays ~0 (frozen at the 0.5*f offset).
// TEST 2 (MOVING_BOUNDARIES): bottom wall of solid cells moving tangentially (u.y=0.1), Couette drag.
// Correct: fluid near the wall is dragged. Broken: ~0, or a corrupted uniform field.
void main_setup() {
{ // TEST 1: VOLUME_FORCE
LBM lbm(64u, 64u, 64u, 0.02f, 1E-5f, 0.0f, 0.0f); // nu=0.02, fx=1E-5, fully periodic
lbm.run(1000u);
lbm.u.read_from_device();
double sum = 0.0;
for(ulong n=0ull; n<lbm.get_N(); n++) sum += (double)lbm.u.x[n];
println("TEST1 VOLUME_FORCE: mean u.x after 1000 steps = "+std::to_string(sum/(double)lbm.get_N())+" (correct: ~0.010000, broken: ~0.000000)");
}
{ // TEST 2: MOVING_BOUNDARIES
LBM lbm(64u, 64u, 64u, 0.02f); // no volume force
for(ulong n=0ull; n<lbm.get_N(); n++) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z);
if(z==0u) { lbm.flags[n] = TYPE_S; lbm.u.y[n] = 0.1f; } // moving bottom wall
}
lbm.run(0u); // initialize fields
lbm.update_moving_boundaries();
lbm.run(3000u);
lbm.u.read_from_device();
double sum = 0.0; ulong cnt = 0ull;
for(ulong n=0ull; n<lbm.get_N(); n++) { uint x=0u, y=0u, z=0u; lbm.coordinates(n, x, y, z);
if(z>=1u&&z<8u) { sum += (double)lbm.u.y[n]; cnt++; } // fluid layer next to the wall
}
println("TEST2 MOVING_BOUNDARIES: mean u.y near wall after 3000 steps = "+std::to_string(sum/(double)cnt)+" (correct: >0.001000, broken: ~0.000000 or corrupted)");
}
}
Build (console only):
g++ src/*.cpp -o bin/FluidX3D -std=c++17 -pthread -O -Wno-comment -I./src/OpenCL/include -framework OpenCL
(Current master needs an unrelated one-line fix to compile on macOS at all — ambiguous to_string in opencl.hpp:628, filed separately. Cast the first argument: to_string((ulong)cl_range_local.get()[0]).)
For the -cl-opt-disable runs, replace the fast-math flags in src/opencl.hpp (get build_options): " -cl-finite-math-only -cl-no-signed-zeros -cl-mad-enable" → " -cl-opt-disable".
Results (Apple M3 Pro, macOS 26.5.2, all deterministic across reruns)
| defines |
kernel build flags |
TEST1 mean u.x (correct ≈0.01) |
TEST2 mean u.y (correct ≈0.003 with SUBGRID / ≈0.075 without) |
| VF+MB+SUBGRID |
default fast-math |
0.000006 — force dead |
0.003151 ✓ |
| VF+MB+SUBGRID |
-cl-opt-disable |
0.008765 ✓ |
0.000002 — wall dead |
| VF+MB |
default fast-math |
0.010612 ✓ |
0.075009 ✓ |
| VF+MB |
-cl-opt-disable |
0.008684 ✓ |
−0.577350 — field corrupted (= −1/√3) |
Trigger isolation for bug 1 (all on default flags): the force dies iff SUBGRID is enabled. Verified alive with VOLUME_FORCE alone; with +SURFACE; with +FORCE_FIELD; with FP32 DDFs (FP16S off); with +GRAPHICS — and dead as soon as SUBGRID is added to any of those (we originally hit it as "dead gravity" in larger free-surface setups with the full extension set).
Workaround for macOS users
Disable SUBGRID (if LES is acceptable to lose) and keep the default build flags: with SUBGRID off, both volume force and moving boundaries work correctly at full speed on macOS 26.5.x. In our free-surface test cases the results then match a Linux/NVIDIA run of the same setups to within a few percent (LES-off difference, not a compiler issue).
Other things we tried, for completeness:
-cl-opt-disable globally: fixes the force but kills moving boundaries (table above) and costs ~4× speed.
__attribute__((optnone)) on stream_collide only (tried on 26.5.1 in a larger build): restored the force along x/z but produced NaNs when the force was along y. Not re-verified on the minimal repro.
- Upstream
b4481ae (unroll-hint removal): no effect on either bug.
The root cause sits in Apple's OpenCL compiler, so there is probably little FluidX3D can do beyond a documented workaround or a startup warning for macOS + SUBGRID + VOLUME_FORCE builds. Happy to run candidate workarounds or additional diagnostics on this machine.
Summary
Since the macOS Tahoe 26.5.1 update (June 2026; still present on 26.5.2), Apple's OpenCL runtime compiler miscompiles FluidX3D kernels on Apple Silicon in two mirrored ways:
SUBGRIDandVOLUME_FORCEare both enabled, the body force silently vanishes — fluid never accelerates, velocity stays frozen at the half-step forcing offset (~0.5*f). No error, no NaN: simulations simply run without gravity/force. WithoutSUBGRIDthe force works correctly.-cl-opt-disable(the natural workaround for bug 1): moving boundaries die instead — a moving solid (MOVING_BOUNDARIES+update_moving_boundaries()) transfers no momentum to the fluid. WithSUBGRIDenabled the response is a silent ~zero; withoutSUBGRIDthe velocity field is corrupted to a uniform unphysical value (exactly −1/√3, deterministic).We found no set of build flags on macOS 26.5.x where volume force and moving boundaries both work while
SUBGRIDis enabled. Everything worked on the same machine before the 26.5.1 OS update, the identical source tree passes equivalent tests on Linux + NVIDIA (driver 550.127.08) with both flag sets, and the bug reproduces on completely unmodified master — so this is an Apple compiler regression, not a FluidX3D bug. Filing it mainly as a heads-up for macOS users (silently wrong physics is the worst failure mode), with a usable workaround below.System
Apple M3 Pro, driver string1.2 1.0 (macOS), OpenCL C 1.2)fbcf16f, 2026-07-11); also reproduces on99a63e5(2026-05-25), with and without theb4481aeunroll-hint removal — makes no differenceMinimal repro
defines.hpp, starting from the shipped defaults: comment outBENCHMARK, enableVOLUME_FORCE,MOVING_BOUNDARIES,SUBGRID. KeepD3Q19,SRT,FP16Sas shipped.Replace
main_setup()insrc/setup.cppwith:Build (console only):
(Current master needs an unrelated one-line fix to compile on macOS at all — ambiguous
to_stringinopencl.hpp:628, filed separately. Cast the first argument:to_string((ulong)cl_range_local.get()[0]).)For the
-cl-opt-disableruns, replace the fast-math flags insrc/opencl.hpp(get build_options):" -cl-finite-math-only -cl-no-signed-zeros -cl-mad-enable"→" -cl-opt-disable".Results (Apple M3 Pro, macOS 26.5.2, all deterministic across reruns)
-cl-opt-disable-cl-opt-disableTrigger isolation for bug 1 (all on default flags): the force dies iff
SUBGRIDis enabled. Verified alive withVOLUME_FORCEalone; with+SURFACE; with+FORCE_FIELD; with FP32 DDFs (FP16Soff); with+GRAPHICS— and dead as soon asSUBGRIDis added to any of those (we originally hit it as "dead gravity" in larger free-surface setups with the full extension set).Workaround for macOS users
Disable
SUBGRID(if LES is acceptable to lose) and keep the default build flags: withSUBGRIDoff, both volume force and moving boundaries work correctly at full speed on macOS 26.5.x. In our free-surface test cases the results then match a Linux/NVIDIA run of the same setups to within a few percent (LES-off difference, not a compiler issue).Other things we tried, for completeness:
-cl-opt-disableglobally: fixes the force but kills moving boundaries (table above) and costs ~4× speed.__attribute__((optnone))onstream_collideonly (tried on 26.5.1 in a larger build): restored the force along x/z but produced NaNs when the force was along y. Not re-verified on the minimal repro.b4481ae(unroll-hint removal): no effect on either bug.The root cause sits in Apple's OpenCL compiler, so there is probably little FluidX3D can do beyond a documented workaround or a startup warning for macOS + SUBGRID + VOLUME_FORCE builds. Happy to run candidate workarounds or additional diagnostics on this machine.