Summary
declare(parallel()) can emit an OpenMP loop with a shared scalar accumulator update (total <- total + x[i]), which is a data race and produces incorrect/nondeterministic results.
Original report and discussion: #88 (comment)
Verified on
- Branch:
main
- Local reproduction date: 2026-02-17
Minimal reproducible example
library(devtools)
load_all(quiet = TRUE)
loop_sum_unsafe <- function(x) {
declare(type(x = double(n)))
total <- 0.0
declare(parallel())
for (i in seq_along(x)) {
total <- total + x[i]
}
total
}
Sys.setenv(OMP_NUM_THREADS = "4", OMP_THREAD_LIMIT = "4", OMP_DYNAMIC = "false")
q_unsafe <- quick(loop_sum_unsafe)
x <- rep(1, 100000L)
vapply(1:6, function(i) q_unsafe(x), numeric(1))
Expected
Each run should return 100000.
Observed
Repeated calls return incorrect and varying results (example from local run):
30961, 31102, 25000, 31544, 30804, 31507
Generated code shape
r2f(loop_sum_unsafe) emits:
!$omp parallel do
do i = 1, size(x)
total = (total + x(i))
end do
!$omp end parallel do
This loop updates shared total without a reduction/private strategy.
Notes
declare(parallel(private = total)) is not currently accepted on main (parallel() does not accept arguments yet.), so users cannot express a reduction/private fix in the current interface.
Summary
declare(parallel())can emit an OpenMP loop with a shared scalar accumulator update (total <- total + x[i]), which is a data race and produces incorrect/nondeterministic results.Original report and discussion: #88 (comment)
Verified on
mainMinimal reproducible example
Expected
Each run should return
100000.Observed
Repeated calls return incorrect and varying results (example from local run):
30961, 31102, 25000, 31544, 30804, 31507Generated code shape
r2f(loop_sum_unsafe)emits:This loop updates shared
totalwithout a reduction/private strategy.Notes
declare(parallel(private = total))is not currently accepted onmain(parallel() does not accept arguments yet.), so users cannot express a reduction/private fix in the current interface.