Skip to content

RMSD: reformulate two loops so their omp simd pragmas can be honoured - #1438

Merged
carlocamilloni merged 2 commits into
plumed:masterfrom
Smith-Group:rmsd-restore-simd-vectorization
Jul 31, 2026
Merged

RMSD: reformulate two loops so their omp simd pragmas can be honoured#1438
carlocamilloni merged 2 commits into
plumed:masterfrom
Smith-Group:rmsd-restore-simd-vectorization

Conversation

@aalhossary

Copy link
Copy Markdown
Contributor
Description

./configure && make, with no arguments at all, fails on any machine whose default C++ compiler is
clang. configure auto-detects OpenMP, and src/tools/Makefile appends -Werror, so the
vectorizer's refusal to honour a #pragma omp simd is fatal:

RMSD.cpp:1443:35: error: loop not vectorized: the optimizer was unable to perform the requested
transformation; the transformation might be disabled or specified as part of an unsupported
transformation ordering [-Werror,-Wpass-failed=transform-warning]
RMSD.cpp:1487:35: error: (the same, in getDDistanceDReference)

Both are attributed to the opening line of the enclosing function, which hides the loop at fault.
Each of the two functions contains exactly one #pragma omp simd, so the attribution is
unambiguous: the loops at lines 1479 and 1530, which are the same loop written twice.

The pragma is kept. Rather than deleting it, the loop body is rewritten so the compiler can act
on it: the loop-invariant (ddist_dc*-csum) is hoisted into a named Vector, and the Vector
expression is expanded into its three components. Both loops now vectorize at width 4 — confirmed
with -Rpass=loop-vectorize — instead of being refused.

I measured the alternatives rather than guessing, all with clang 20.1.1 on the same tree:

variant result
unchanged ✗ 2 errors
braces around the if body ✗ still fails
hoisting the invariant only ✗ still fails
binding Vector& dv=derivatives[iat] in the body ✗ still fails
flatten to double* + manual 3-way unroll ✓ vectorizes, but the pointer is UB when n==0
per-component derivatives[iat][k] ✓ vectorizes — chosen

The Vector arithmetic creates temporaries the vectorizer cannot see through, and naming the
element through a reference reintroduces them; only the direct per-component form works. Hoisting
alone is not sufficient, but it is necessary, so both changes are needed together.

No other pragma in the file is touched. The other eight active ones already vectorize — their
enclosing functions produce no remark. Two more, on identically shaped std::vector<Vector> loops
at lines 1339 and 1426, were commented out long ago with the note that they "lead to incorrect
results with INTEL compiler"
; the two fixed here had exactly that shape, so this also removes a
latent instance of that hazard.

Why CI did not catch it

No job builds clang with OpenMP enabled. linuxWF.yml uses gcc and icpx; macWF.yml's
macsimple job uses AppleClang without libomp, so #pragma omp simd is inert there and
-Wno-unknown-pragmas hides the rest. gcc has no -Wpass-failed diagnostic at all. The default
build on a clang host is the configuration nobody exercises.

This is the same class of problem as #1437, found by scanning the whole tree for it afterwards.
omp simd occurs in only two files in the entire repository — src/colvar/PathMSDBase.cpp, fixed
by #1437, and src/tools/RMSD.cpp — and there are no #pragma unroll, #pragma clang loop,
ivdep or vector directives anywhere. With this change, an --enable-modules=all build is free
of -Wpass-failed entirely.

Target release

I would like my code to appear in release 2.11 (master).

v2.10 carries the same two pragmas but not the -Werror in src/tools/Makefile, which arrived
on master only. There it is a warning rather than a build failure, so this is not a forward-merge
candidate unless you would like the warning silenced there too.

Type of contribution
  • changes to code or doc authored by PLUMED developers
  • changes to a module not authored by you
  • new module contribution or edit of a module authored by you
Copyright
  • I agree to transfer the copyright of the code I have written to the PLUMED developers or to
    the author of the code I am modifying.
  • the code I am contributing is not mine and I am making it available under LGPL
Tests
  • I added a new regtest or modified an existing regtest to validate my changes.
  • I verified that all regtests are passed successfully on GitHub Actions.

No new regtest is added: this changes neither behaviour nor interface, so there is nothing new to
assert. The existing tests are the check, and they already encode the expected colvars and
derivatives.

Locally I ran the full suite twice against the same tree, once with the change and once with
src/tools/RMSD.cpp reverted to master, so the comparison isolates this file:

                                tests run    failing tests
master, RMSD.cpp reverted           824           20
this branch                         824           20     <- identical set

The failure lists are byte-identical, so this change introduces none and hides none. All 20 are
pre-existing and unrelated: 4 are basic/rt-make-*, which compile against an installed plumed
(this tree was never make installed), and 16 are ves/rt-bf-* basis-function tests. The run
enables every module (--enable-modules=all) with --disable-mpi, which is why the count is
non-zero at all.

The 17 regtests that exercise these code paths all pass, including rt-rmsd-displace — the
align != displace case, which is the branch the modified loops actually sit in — and
rt65-rmsd2, rt-close-structure and rt64-pca, the three named in the Intel comment above.

Builds verified:

  • plain ./configure && make (clang auto-detected, OpenMP auto-enabled, default modules) — fails
    before this change, clean after: 380 files, 0 errors.
  • --enable-modules=all with clang + OpenMP + -O3 -march=skylake-avx512 — clean, 0 -Wpass-failed
    across all 56 modules.
  • make -C astyle leaves the file unchanged.

A plain `./configure && make` fails on any machine whose default C++ compiler is
clang: configure enables OpenMP automatically, and src/tools/Makefile appends
-Werror, so the vectorizer's refusal is fatal.

    RMSD.cpp:1443:35: error: loop not vectorized: the optimizer was unable to
    perform the requested transformation; the transformation might be disabled or
    specified as part of an unsupported transformation ordering
    [-Werror,-Wpass-failed=transform-warning]
    RMSD.cpp:1487:35: error: (the same, in getDDistanceDReference)

Both are attributed to the opening line of the enclosing function, which hides
the loop at fault. Each of the two functions contains exactly one
`#pragma omp simd`, so the attribution is unambiguous: the loops at lines 1479
and 1530.

The pragma is kept. The loop body is rewritten so the compiler can act on it:
the loop-invariant `(ddist_dc*-csum)` is hoisted into a named Vector, and the
Vector expression is expanded into its three components. Both loops now
vectorize at width 4, confirmed with -Rpass=loop-vectorize, instead of being
refused.

Alternatives measured with clang 20.1.1, all on the same tree:

    braces around the if body                       still fails
    hoisting the invariant only                     still fails
    binding `Vector& dv=derivatives[iat]`           still fails
    flatten to `double*` + manual 3-way unroll      vectorizes, but the pointer
                                                    is UB when n==0
    per-component `derivatives[iat][k]`             vectorizes   <- chosen

The Vector arithmetic creates temporaries the vectorizer cannot see through, and
naming the element through a reference reintroduces them; only the direct
per-component form works.

No other pragma in the file is touched. The other eight active ones already
vectorize. Two more, on identically shaped std::vector<Vector> loops at lines
1339 and 1426, were commented out long ago because they gave wrong results with
the Intel compiler; these two had the same shape.

Behaviour is unchanged, and the 17 regtests that exercise these paths pass,
including rt-rmsd-displace, which is the align != displace case that actually
enters the modified branch.

This is the same class of problem as plumed#1437. It escapes CI because no job builds
clang with OpenMP enabled: the Linux workflow uses gcc and icpx, and macsimple
uses AppleClang without libomp, where `#pragma omp simd` is inert.
The first commit fixed the two loops that fail at -O3. Building PLUMED with
mpicxx at -O2 shows a third one, which -O3 happens to vectorize:

    RMSD.cpp:1361:22: error: loop not vectorized: the optimizer was unable to
    perform the requested transformation [-Werror,-Wpass-failed=transform-warning]

The remark is attributed to getDistance()'s opening line; the pragma is the
`omp simd reduction(+:localDist)` at line 1374. Its body branches on alEqDis
and safe, both of which are loop-invariant members, so the loop carries a
per-iteration test the vectorizer will not always unswitch by itself.

Hoisting the two branches out of the loop makes it vectorize at -O2 as well.
This is a pure unswitching: both conditions are invariant, the summation order
is unchanged, and the arithmetic is untouched, so results are bit-identical.

Measured on this file with clang 20.1.1, counting -Wpass-failed diagnostics
(-march makes no difference at any level):

    -O level     before    after
    -O0            0         0
    -O1            5         4
    -Os            8         7
    -O2            3         0
    -O3            2         0

-O1 and -Os remain non-clean, but not for a reason any source change can fix:
clang's loop vectorizer is effectively disabled there, so every `omp simd` in
the file is refused, including the ones that are perfectly good at -O2. Making
PLUMED buildable at those levels would mean not combining -Werror with
-Wpass-failed in the module makefiles; that is a build-system decision and is
deliberately left out of this PR.
@aalhossary

Copy link
Copy Markdown
Contributor Author

Pushed a second commit: a third loop in this file needed the same treatment, and I had missed it because the failure set depends on the optimization level.

Building PLUMED with mpicxx at -O2 (rather than configure's default -O3) also rejects the omp simd reduction(+:localDist) at line 1374, in getDistance(). Its body branches on alEqDis and safe, both loop-invariant members, so the loop carries a per-iteration test that the vectorizer does not always unswitch on its own. Hoisting the two branches out of the loop fixes it. That is a pure unswitching — both conditions are invariant, the summation order is unchanged, the arithmetic is untouched — so results are bit-identical.

Counting -Wpass-failed diagnostics in this file with clang 20.1.1 (-march makes no difference at any level):

-O level before after
-O0 0 0
-O1 5 4
-Os 8 7
-O2 3 0
-O3 2 0

So the PR now makes the file clean at both optimization levels where the vectorizer actually runs.

-O1 and -Os stay non-clean, and I want to flag that honestly rather than leave it implied: there clang's loop vectorizer is effectively disabled, so every omp simd in the file is refused — including the ones that are perfectly fine at -O2. No source change can fix that. It would need the module makefiles not to combine -Werror with -Wpass-failed, which is a build-system decision, so I have deliberately kept it out of this PR. Happy to open a separate issue if you would like -O1/-Os builds to work under clang.

Re-verified after the second commit: full regtest suite, 824 tests, same 20 pre-existing failures as master with src/tools/RMSD.cpp reverted — byte-identical failure lists.

@aalhossary
aalhossary changed the base branch from master to v2.10 July 31, 2026 08:07
@aalhossary
aalhossary changed the base branch from v2.10 to master July 31, 2026 08:08
@aalhossary

aalhossary commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@carlocamilloni Should this PR go to master as #1437 did or better go to v2.10?
If 2.10, then I will have to rebase it, and you will need to cherry pick the already merged commit into v2.10.

@carlocamilloni
carlocamilloni merged commit 675f720 into plumed:master Jul 31, 2026
25 of 26 checks passed
@aalhossary
aalhossary deleted the rmsd-restore-simd-vectorization branch July 31, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants