Skip to content

Conversation

sdesmalen-arm
Copy link
Collaborator

@sdesmalen-arm sdesmalen-arm commented Jul 25, 2025

Previously Clang would give an unwarranted error on the capture of '&a' in the function below, even though the parent function and the lambda are both __arm_streaming functions, when the target is compiled with +sme only.

  void test_both_streaming(int32_t *out) __arm_streaming {
    svint32_t a;
    [&a, &out]() __arm_streaming {
      ^
      error: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function

      a = svdup_s32(1);
      svst1(svptrue_b32(), out, a);
    }();
  }

That seems to happen because when checkTypeSupport is called the FunctionDecl context of the lambda isn't yet complete and FD->getType() returns a Null QualTy.

This is loosely related to #94976 which removed a FD->hasBody() check in the same place, because hasBody() may incorrectly return false when Clang is still processing a function.

@sdesmalen-arm sdesmalen-arm requested review from hvdijk and MacDue July 25, 2025 08:47
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 25, 2025

@llvm/pr-subscribers-clang

Author: Sander de Smalen (sdesmalen-arm)

Changes

Previously Clang would give an unwarranted error on the capture of '&a' in the function below, even though the parent function and the lambda are both __arm_streaming functions, when the target is compiled with +sme only.

void test_both_streaming(int32_t *out) __arm_streaming {
svint32_t a;
&a, &out __arm_streaming {
^
error: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function

  a = svdup_s32(1);
  svst1(svptrue_b32(), out, a);
}();

}

That seems to happen because when checkTypeSupport is called the FunctionDecl context of the lambda isn't yet complete and FD->getType() returns a Null QualTy.

This is loosely related to #94976 which removed a FD->hasBody() check in the same place, because hasBody() may incorrectly return false when Clang is still processing a function.


Full diff: https://github.com/llvm/llvm-project/pull/150592.diff

3 Files Affected:

  • (modified) clang/lib/AST/Decl.cpp (+3-5)
  • (modified) clang/lib/Sema/Sema.cpp (+2-3)
  • (added) clang/test/Sema/aarch64-sme-attrs-without-sve.cpp (+31)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 83fcd87aec2f8..748d960e63431 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -5988,11 +5988,9 @@ bool clang::IsArmStreamingFunction(const FunctionDecl *FD,
     if (FD->hasAttr<ArmLocallyStreamingAttr>())
       return true;
 
-  if (const Type *Ty = FD->getType().getTypePtrOrNull())
-    if (const auto *FPT = Ty->getAs<FunctionProtoType>())
-      if (FPT->getAArch64SMEAttributes() &
-          FunctionType::SME_PStateSMEnabledMask)
-        return true;
+  if (const auto *FPT = FD->getType()->getAs<FunctionProtoType>())
+    if (FPT->getAArch64SMEAttributes() & FunctionType::SME_PStateSMEnabledMask)
+      return true;
 
   return false;
 }
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 56608e990fd50..98e155d27ef13 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2249,16 +2249,15 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
     }
 
     // Don't allow SVE types in functions without a SVE target.
-    if (Ty->isSVESizelessBuiltinType() && FD) {
+    if (Ty->isSVESizelessBuiltinType() && FD && !FD->getType().isNull()) {
       llvm::StringMap<bool> CallerFeatureMap;
       Context.getFunctionFeatureMap(CallerFeatureMap, FD);
       if (!Builtin::evaluateRequiredTargetFeatures("sve", CallerFeatureMap)) {
         if (!Builtin::evaluateRequiredTargetFeatures("sme", CallerFeatureMap))
           Diag(Loc, diag::err_sve_vector_in_non_sve_target) << Ty;
         else if (!IsArmStreamingFunction(FD,
-                                         /*IncludeLocallyStreaming=*/true)) {
+                                         /*IncludeLocallyStreaming=*/true))
           Diag(Loc, diag::err_sve_vector_in_non_streaming_function) << Ty;
-        }
       }
     }
 
diff --git a/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp b/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp
new file mode 100644
index 0000000000000..950f26e3a144e
--- /dev/null
+++ b/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple aarch64-none-linux-gnu -target-feature +sme -fsyntax-only -verify %s
+
+#include <arm_sme.h>
+
+void test_streaming(svint32_t *out, svint32_t *in) __arm_streaming {
+  *out = *in;
+}
+
+void test_non_streaming(svint32_t *out, svint32_t *in) {
+  *out = *in; // expected-error {{SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function}} \
+                 expected-error {{SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function}}
+}
+
+// This previously led to a diagnostic that '&a' could not be used in a non-streaming function,
+// even though all functions are streaming.
+void test_both_streaming(int32_t *out) __arm_streaming{
+  svint32_t a;
+  [&a, &out]() __arm_streaming {
+    a = svdup_s32(1);
+    svst1(svptrue_b32(), out, a);
+  }();
+}
+
+void test_lambda_streaming(int32_t *out) {
+  svint32_t a; // expected-error {{SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function}}
+  [&a, &out]() __arm_streaming {
+    a = 1;
+    svst1(svptrue_b32(), out, a);
+  }();
+}
+

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks like it should not be necessary for your main bugfix, this change looks like an micro-optimization based on the knowledge that we know IsArmStreamingFunction is now never called with a FunctionDecl that has a null type, is that right? If so, I do not think that this function should be assuming that. It may be right for all the code that is in Clang, but this function is available in the public headers, we cannot assume that we know all the places where it gets called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it was an intentional change because I think this function must be able to assume it is called with a FunctionDecl that is sufficiently complete. The if-block here was just hiding some unexpected issue.

If for example, I were to call FD->dump() in this function, then dump will run into an assertion that the type is null. If this function would need to assume the type can be null, then I think every function in Clang must have this check. By assuming the type is not null (and it running into an assertion or segfault), at least Clang won't (possibly quietly) do the wrong thing and it might expose other places we'd need to patch up. This was the only place I could find. The call to IsArmStreamingFunction below cannot be tested without +sve anyway because arm_sve_vector_bits requires it to be available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing a previously valid call to have undefined behavior does not sound like a good idea to me. If we want to say that it is invalid to call this function on something that is currently being defined -- I don't feel too strongly on that -- given that it was previously valid, at the least there should be an assert so that it fails reliably (in assertion-enabled builds).

But even then, that's a change that's not needed for your bugfix, so personally, I'd rather have that not in this PR: get the bugfix merged first, then do the IsArmStreamingFunction as a followup PR so that if there is any fallout from that (possibly because of external callers) and it needs to be reverted, that doesn't take the bugfix with it.

Copy link
Member

@MacDue MacDue Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If for example, I were to call FD->dump() in this function, then dump will run into an assertion that the type is null.

An explicit assert should back any assumptions like this; dereferencing a null-pointer without an assert can lead to less obvious errors/issues (and it's unclear what the author's intent was).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I said "the type is null", I meant that the QualType is QualType() <=> QualType::isNull() == true. With asserts enabled the operator-> that returns the Type* has an explicit assert that the type is not QualType():
https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/Type.h#L952
I'm happy to add an explicit assert in this function and to commit this as a separate change though.

Copy link
Collaborator Author

@sdesmalen-arm sdesmalen-arm Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that it was previously valid

It was never previously valid to call it with an object that is still in the process of being defined, we just hid the issue by returning a bogus result.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operator-> that returns the Type* has an explicit assert that the type is not QualType():

Ah, okay 👍

I'm happy to add an explicit assert in this function and to commit this as a separate change though.

I think it'd still be good to add an assert here. It's not obvious that the QualType assertion is hit before you dereference Type* pointer (at a glance), and I think it's better to fail early.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was never previously valid to call it with an object that is still in the process of being defined, we just hid the issue by returning a bogus result.

Let me rephrase: previously, calling this with an object that was still in the process of being defined was something that had well-defined behaviour by the C++ language rules, was something that Clang itself did, was something that had code written to handle specifically that, and was something that violated no requirement expressed anywhere in the LLVM code or documentation. Even if it was not intended to be valid, there was no way for anyone to infer that.

That said, regardless of whether we say it was or wasn't valid before, I'm fine with deciding it's definitely not going to be valid going forward (in that future PR). This PR, and that future PR, they both look good to me.

Copy link
Member

@MacDue MacDue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…mode' diagnostic.

Previously Clang would give an unwarranted error on the capture of '&a'
in the function below, even though the parent function and the lambda
are both `__arm_streaming` functions, when the target is compiled with
+sme only.

  void test_both_streaming(int32_t *out) __arm_streaming {
    svint32_t a;
    [&a, &out]() __arm_streaming {
      ^
      error: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function

      a = svdup_s32(1);
      svst1(svptrue_b32(), out, a);
    }();
  }

That seems to happen because when `checkTypeSupport` is called
the `FunctionDecl` context of the lambda isn't yet complete and
`FD->getType()` returns a Null `QualTy`.
@sdesmalen-arm sdesmalen-arm force-pushed the sme-fix-lambda-capture-streaming-mode branch from 8f9447e to 593f070 Compare July 30, 2025 13:48
@sdesmalen-arm sdesmalen-arm merged commit 83d3770 into llvm:main Jul 30, 2025
9 checks passed
sdesmalen-arm added a commit that referenced this pull request Jul 30, 2025
This follows from a conversation on #150592 where we decided to
split out this change and commit it separately.

The rationale is that FunctionDecl must be sufficiently parsed/created
in order to tell whether it is a streaming function.
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 30, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-sie-ubuntu-fast running on sie-linux-worker while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/144/builds/31604

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang :: Sema/aarch64-sme-attrs-without-sve.cpp' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/22/include -nostdsysteminc -triple aarch64-none-linux-gnu -target-feature +sme -fsyntax-only -verify /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp # RUN: at line 1
+ /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/22/include -nostdsysteminc -triple aarch64-none-linux-gnu -target-feature +sme -fsyntax-only -verify /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp
error: 'expected-error' diagnostics expected but not seen: 
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 10: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 10: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 25: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 43: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 52: SVE vector type 'svint32_t' (aka '__SVInt32_t') cannot be used in a non-streaming function
error: 'expected-error' diagnostics seen but not expected: 
  File /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/Sema/aarch64-sme-attrs-without-sve.cpp Line 3: 'arm_sme.h' file not found
6 errors generated.

--

********************


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants