|
2 | 2 |
|
3 | 3 | from mfc.lint_source import ( |
4 | 4 | _extract_bcast_roots, |
| 5 | + check_device_routine_element_args, |
5 | 6 | check_double_precision, |
6 | 7 | check_integer_wp, |
7 | 8 | check_manual_registry_bcasts, |
@@ -148,3 +149,123 @@ def test_manual_residue_is_clean(tmp_path): |
148 | 149 | _write_proxy(tmp_path, "simulation", body) |
149 | 150 |
|
150 | 151 | assert check_manual_registry_bcasts(tmp_path) == [] |
| 152 | + |
| 153 | + |
| 154 | +_LOOPED = """ subroutine s_curve(rho, i, p) |
| 155 | + $:GPU_ROUTINE(parallelism='[seq]') |
| 156 | + real(wp), intent(in) :: rho |
| 157 | + integer, intent(in) :: i |
| 158 | + real(wp), intent(out) :: p |
| 159 | + integer :: it |
| 160 | + $:GPU_LOOP(parallelism='[seq]') |
| 161 | + do it = 1, 8 |
| 162 | + p = p + rho |
| 163 | + end do |
| 164 | + end subroutine s_curve |
| 165 | + subroutine s_wrap(rho, i, p) |
| 166 | + $:GPU_ROUTINE(parallelism='[seq]') |
| 167 | + real(wp), intent(in) :: rho |
| 168 | + integer, intent(in) :: i |
| 169 | + real(wp), intent(out) :: p |
| 170 | + call s_curve(rho, i, p) |
| 171 | + end subroutine s_wrap |
| 172 | + subroutine s_plain(rho, i, p) |
| 173 | + $:GPU_ROUTINE(parallelism='[seq]') |
| 174 | + real(wp), intent(in) :: rho |
| 175 | + integer, intent(in) :: i |
| 176 | + real(wp), intent(out) :: p |
| 177 | + p = rho |
| 178 | + end subroutine s_plain |
| 179 | +""" |
| 180 | + |
| 181 | + |
| 182 | +def _KERNEL(body: str) -> str: |
| 183 | + return " $:GPU_PARALLEL_LOOP(collapse=3)\n " + body + "\n $:END_GPU_PARALLEL_LOOP()\n" |
| 184 | + |
| 185 | + |
| 186 | +def test_host_call_sites_are_not_flagged(tmp_path): |
| 187 | + _write_src(tmp_path, "simulation/m_x.fpp", _LOOPED + " call s_curve(q(1)%sf(j, k, l), 1, out(k, l, q))\n") |
| 188 | + assert check_device_routine_element_args(tmp_path) == [] |
| 189 | + |
| 190 | + |
| 191 | +def test_element_into_looped_device_routine_is_flagged(tmp_path): |
| 192 | + _write_src(tmp_path, "simulation/m_x.fpp", _LOOPED + _KERNEL("call s_curve(q(1)%sf(j, k, l), 1, out(k, l, q))")) |
| 193 | + errors = check_device_routine_element_args(tmp_path) |
| 194 | + assert len(errors) == 2 |
| 195 | + assert "q(1)%sf(j, k, l)" in errors[0] and "out(k, l, q)" in errors[1] |
| 196 | + |
| 197 | + |
| 198 | +def test_element_reaches_the_loop_through_a_caller(tmp_path): |
| 199 | + _write_src(tmp_path, "simulation/m_x.fpp", _LOOPED + _KERNEL("call s_wrap(pres, 1, blkmod(k, &\n & l, q))")) |
| 200 | + errors = check_device_routine_element_args(tmp_path) |
| 201 | + assert len(errors) == 1 and "s_wrap" in errors[0] |
| 202 | + |
| 203 | + |
| 204 | +def test_scalars_expressions_and_loopless_routines_pass(tmp_path): |
| 205 | + _write_src( |
| 206 | + tmp_path, |
| 207 | + "simulation/m_x.fpp", |
| 208 | + _LOOPED |
| 209 | + + _KERNEL("call s_curve(alpha_rho(i)/max(alpha(i), sgm_eps), i, p_i)\n call s_plain(q(1)%sf(j, k, l), 1, out(k, l, q))\n call s_curve(real(q(1)%sf(j, k, l), wp), 1, p_i)"), |
| 210 | + ) |
| 211 | + assert check_device_routine_element_args(tmp_path) == [] |
| 212 | + |
| 213 | + |
| 214 | +def test_loop_inside_a_device_function_counts_and_propagates(tmp_path): |
| 215 | + src = """ function f_looped(x, i) result(y) |
| 216 | + $:GPU_ROUTINE(function_name='f_looped', parallelism='[seq]') |
| 217 | + real(wp), intent(in) :: x |
| 218 | + integer, intent(in) :: i |
| 219 | + real(wp) :: y |
| 220 | + integer :: it |
| 221 | + y = x |
| 222 | + $:GPU_LOOP(parallelism='[seq]') |
| 223 | + do it = 1, 8 |
| 224 | + y = y + 1._wp |
| 225 | + end do |
| 226 | + end function f_looped |
| 227 | + subroutine s_via_function(x, i, y) |
| 228 | + $:GPU_ROUTINE(parallelism='[seq]') |
| 229 | + real(wp), intent(in) :: x |
| 230 | + integer, intent(in) :: i |
| 231 | + real(wp), intent(out) :: y |
| 232 | + y = f_looped(x, i) |
| 233 | + end subroutine s_via_function |
| 234 | +""" |
| 235 | + calls = "out(k, l, q) = f_looped(q(1)%sf(k, l, q), 1)\n call s_via_function(q(1)%sf(k, l, q), 1, tmp)\n tmp = f_looped(p_scalar, 1)" |
| 236 | + _write_src(tmp_path, "simulation/m_x.fpp", src + _KERNEL(calls)) |
| 237 | + errors = check_device_routine_element_args(tmp_path) |
| 238 | + assert [e.split("`")[3] for e in errors] == ["f_looped", "s_via_function"] |
| 239 | + |
| 240 | + |
| 241 | +def test_constructor_commas_and_unprefixed_functions_and_contained_scoping(tmp_path): |
| 242 | + src = """ function g_looped(x) result(y) |
| 243 | + $:GPU_ROUTINE(function_name='g_looped', parallelism='[seq]') |
| 244 | + real(wp), intent(in) :: x |
| 245 | + real(wp) :: y |
| 246 | + integer :: it |
| 247 | + y = x |
| 248 | + $:GPU_LOOP(parallelism='[seq]') |
| 249 | + do it = 1, 8 |
| 250 | + y = y + 1._wp |
| 251 | + end do |
| 252 | + end function g_looped |
| 253 | + subroutine s_outer(a, b) |
| 254 | + real(wp), intent(in) :: a |
| 255 | + real(wp), intent(out) :: b |
| 256 | + b = a |
| 257 | + contains |
| 258 | + subroutine s_inner(x, y) |
| 259 | + $:GPU_ROUTINE(parallelism='[seq]') |
| 260 | + real(wp), intent(in) :: x |
| 261 | + real(wp), intent(out) :: y |
| 262 | + y = g_looped(x) |
| 263 | + end subroutine s_inner |
| 264 | + end subroutine s_outer |
| 265 | +""" |
| 266 | + calls = "b = g_looped(q(1)%sf(k, l, q))\\n c = g_looped(sum([v(1), v(2)]))\\n call s_outer(q(1)%sf(k, l, q), tmp)" |
| 267 | + _write_src(tmp_path, "simulation/m_x.fpp", src + _KERNEL(calls)) |
| 268 | + errors = check_device_routine_element_args(tmp_path) |
| 269 | + # the unprefixed function is found by name; the array constructor is not split into a fake element; |
| 270 | + # s_outer is not tainted by the loop that only its contained s_inner reaches (and is not a device routine) |
| 271 | + assert [e.split("`")[1] for e in errors] == ["q(1)%sf(k, l, q)"] |
0 commit comments