[flang-commits] [flang] [llvm] [flang][cuda][semantics] Reject unguarded host-only and device-only calls in CUDA HOST, DEVICE procedures (PR #228176)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Mon Oct 5 17:51:31 PDT 2026


================
@@ -644,42 +751,111 @@ template <bool IsCUFKernelDo> class DeviceContextChecker {
                 ErrorIfHostSymbol(assign->rhs, source);
               }
               if (auto msg{ActionStmtChecker<IsCUFKernelDo>::WhyNotOk(
-                      context_, x, isHostDevice)}) {
+                      context_, x, callContext_)}) {
                 context_.Say(source, std::move(*msg));
               }
             },
             [&](const auto &x) {
               if (auto msg{ActionStmtChecker<IsCUFKernelDo>::WhyNotOk(
-                      context_, x, isHostDevice)}) {
+                      context_, x, callContext_)}) {
                 context_.Say(source, std::move(*msg));
               }
             },
         },
         stmt.u);
   }
   void Check(const parser::IfConstruct &ic) {
+    const CallContext incoming{callContext_};
     const auto &ifS{std::get<parser::Statement<parser::IfThenStmt>>(ic.t)};
-    CheckUnwrappedExpr(context_, ifS.source,
-        std::get<parser::ScalarLogicalExpr>(ifS.statement.t), isHostDevice);
+    const auto &condition{std::get<parser::ScalarLogicalExpr>(ifS.statement.t)};
+    CheckUnwrappedExpr(context_, ifS.source, condition, callContext_);
+    AllowGuardedCalls(condition);
+    const CallContext branchContext{callContext_};
     Check(std::get<parser::Block>(ic.t));
-    for (const auto &eib :
-        std::get<std::list<parser::IfConstruct::ElseIfBlock>>(ic.t)) {
+    const auto &elseIfBlocks{
+        std::get<std::list<parser::IfConstruct::ElseIfBlock>>(ic.t)};
+    for (const auto &eib : elseIfBlocks) {
+      // An ELSEIF guard applies to its own arm and its plain ELSE, not to a
+      // later unrelated ELSEIF. Preserve a guard on the initial IF, if any.
+      callContext_ = branchContext;
----------------
eugeneepshteyn wrote:

(This comment was generated with the help of AI)

An arm after `else if (on_device())` only runs when `on_device()` was false, so only in the host copy. Resetting to the IF's context makes these host-only calls errors; both are accepted on main:

```fortran
module elseif_after_guard
contains
  subroutine host_only()
  end subroutine
  attributes(device) subroutine device_only()
  end subroutine

  ! The ELSE IF (y) arm runs only when on_device() is false.
  attributes(host,device) subroutine later_else_if(x, y)
    logical, value :: x, y
    if (x) then
      continue
    else if (on_device()) then
      call device_only()
    else if (y) then
      call host_only()
    end if
  end subroutine

  ! The plain ELSE also runs only when on_device() is false.
  attributes(host,device) subroutine later_else(x, y)
    logical, value :: x, y
    if (x) then
      continue
    else if (on_device()) then
      call device_only()
    else if (y) then
      continue
    else
      call host_only()
    end if
  end subroutine
end module
```

```console
$ flang -fsyntax-only -x cuda elseif_after_guard.cuf
error: Semantic errors in elseif_after_guard.cuf
elseif_after_guard.cuf:16:7: error: 'host_only' may not be called in device code
        call host_only()
        ^^^^^^^^^^^^^^^^
elseif_after_guard.cuf:30:7: error: 'host_only' may not be called in device code
        call host_only()
        ^^^^^^^^^^^^^^^^
```

The same control flow written as a nested IF inside the ELSE is accepted. Since the PR prefers not to error on valid code, I'd drop the reset: the arms then stay under the ELSE IF's guard. That also stops the `device_only` call at `cuf-hostdevice-return-guard.cuf:194` from being diagnosed, which belongs to the already-documented permissive class. Line 190 there currently pins the false positive.


https://github.com/llvm/llvm-project/pull/228176


More information about the flang-commits mailing list