[clang] [Clang][Sema] Extend test coverage for SVE/SME builtin usage. (PR #156908)

Graham Hunter via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 8 07:27:15 PDT 2025


================
@@ -0,0 +1,539 @@
+#!/usr/bin/env python3
+"""
+Generate C test files that call ACLE builtins found in a JSON manifest.
+
+Expected JSON input format (array of objects):
+[
+  {
+    "guard": "sve,(sve2p1|sme)",
+    "streaming_guard": "sme",
+    "flags": "feature-dependent",
+    "builtin": "svint16_t svrevd_s16_z(svbool_t, svint16_t);"
+  },
+  ...
+]
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+import sys
+from collections import defaultdict
+from dataclasses import dataclass
+from enum import Enum
+from itertools import product
+from pathlib import Path
+from typing import Any, Dict, Iterable, List, Sequence, Tuple
+
+assert sys.version_info >= (3, 7), "Only Python 3.7+ is supported."
+
+
+# Are we testing arm_sve.h or arm_sme.h based builtins.
+class Mode(Enum):
+    SVE = "sve"
+    SME = "sme"
+
+
+class FunctionType(Enum):
+    NORMAL = "normal"
+    STREAMING = "streaming"
+    STREAMING_COMPATIBLE = "streaming-compatible"
+
+
+# Builtins are grouped by their required features.
+ at dataclass(frozen=True, order=True)
+class BuiltinContext:
+    guard: str
+    streaming_guard: str
+    flags: tuple[str, ...]
+
+    def __str__(self) -> str:
+        return (
+            f"// Properties: "
+            f'guard="{self.guard}" '
+            f'streaming_guard="{self.streaming_guard}" '
+            f'flags="{",".join(self.flags)}"'
+        )
+
+    @classmethod
+    def from_json(cls, obj: dict[str, Any]) -> "BuiltinContext":
+        flags = tuple(p.strip() for p in obj["flags"].split(",") if p.strip())
+        return cls(obj["guard"], obj["streaming_guard"], flags)
+
+
+# --- Parsing builtins -------------------------------------------------------
+
+# Captures the full function *declaration* inside the builtin string, e.g.:
+#   "svint16_t svrevd_s16_z(svbool_t, svint16_t);"
+# group(1) => "svint16_t svrevd_s16_z"
+# group(2) => "svbool_t, svint16_t"
+FUNC_RE = re.compile(r"^\s*([a-zA-Z_][\w\s\*]*[\w\*])\s*\(\s*([^)]*)\s*\)\s*;\s*$")
+
+# Pulls the final word out of the left side (the function name).
+NAME_RE = re.compile(r"([a-zA-Z_][\w]*)\s*$")
+
+
+def parse_builtin_declaration(decl: str) -> Tuple[str, List[str]]:
+    """Return (func_name, param_types) from a builtin declaration string.
+
+    Example:
+      decl = "svint16_t svrevd_s16_z(svbool_t, svint16_t);"
+      => ("svrevd_s16_z", ["svbool_t", "svint16_t"])
+    """
+    m = FUNC_RE.match(decl)
+    if not m:
+        raise ValueError(f"Unrecognized builtin declaration syntax: {decl!r}")
+
+    left = m.group(1)  # return type + name
+    params = m.group(2).strip()
+
+    name_m = NAME_RE.search(left)
+    if not name_m:
+        raise ValueError(f"Could not find function name in: {decl!r}")
+    func_name = name_m.group(1)
+
+    if not params:
+        param_types: List[str] = []
+    else:
+        # Split by commas respecting no pointers/arrays with commas (not expected here)
+        param_types = [p.strip() for p in params.split(",") if p.strip()]
+
+    return func_name, param_types
+
+
+# --- Variable synthesis -----------------------------------------------------
+
+# Pick a safe (ideally non-zero) value for literal types
+LITERAL_TYPES_MAP: dict[str, str] = {
+    "ImmCheck0_0": "0",
+    "ImmCheck0_1": "1",
+    "ImmCheck0_2": "2",
+    "ImmCheck0_3": "2",
+    "ImmCheck0_7": "2",
+    "ImmCheck0_13": "2",
+    "ImmCheck0_15": "2",
+    "ImmCheck0_31": "2",
+    "ImmCheck0_63": "2",
+    "ImmCheck0_255": "2",
+    "ImmCheck1_1": "1",
+    "ImmCheck1_3": "2",
+    "ImmCheck1_7": "2",
+    "ImmCheck1_16": "2",
+    "ImmCheck1_32": "2",
+    "ImmCheck1_64": "2",
+    "ImmCheck2_4_Mul2": "2",
+    "ImmCheckComplexRot90_270": "90",
+    "ImmCheckComplexRotAll90": "90",
+    "ImmCheckCvt": "2",
+    "ImmCheckExtract": "2",
+    "ImmCheckLaneIndexCompRotate": "1",
+    "ImmCheckLaneIndexDot": "1",
+    "ImmCheckLaneIndex": "1",
+    "ImmCheckShiftLeft": "2",
+    "ImmCheckShiftRightNarrow": "2",
+    "ImmCheckShiftRight": "2",
+    "enum svpattern": "SV_MUL3",
+    "enum svprfop": "SV_PSTL1KEEP",
+    "void": "",
+}
+
+
+def make_arg_for_type(ty: str) -> Tuple[str, str]:
+    """Return (var_decl, var_use) for a parameter type.
+
+    Literal types return an empty declaration and a value that will be accepted
+    by clang's semantic literal validation.
+    """
+    # Compress whitespace and remove non-relevant qualifiers.
+    ty = re.sub(r"\s+", " ", ty.strip()).replace(" const", "")
+    if ty in LITERAL_TYPES_MAP:
+        return "", LITERAL_TYPES_MAP[ty]
+
+    name = ty.replace(" ", "_").replace("*", "ptr") + "_val"
+    return f"{ty} {name};", name
+
+
+# NOTE: Parsing is limited to the minimum required for guard strings.
+# Specifically the expected input is of the form:
+#   feat1,feat2,...(feat3 | feat4 | ...),...
+def expand_feature_guard(
+    guard: str, flags: Sequence[str], base_feature: str = ""
+) -> list[set[str]]:
+    """
+    Expand a guard expression where ',' = AND and '|' = OR, with parentheses
+    grouping OR-expressions. Returns a list of feature sets.
+    """
+    if not guard:
+        return []
+
+    parts = re.split(r",(?![^(]*\))", guard)
+
+    choices_per_part = []
+    for part in parts:
+        if part.startswith("(") and part.endswith(")"):
+            choices_per_part.append(part[1:-1].split("|"))
+        else:
+            choices_per_part.append([part])
+
+    # Add feature that is common to all
+    if base_feature:
+        choices_per_part.append([base_feature])
+
+    if "requires-zt" in flags:
+        choices_per_part.append(["sme2"])
+
+    # construct list of feature sets
+    results = []
+    for choice in product(*choices_per_part):
+        choice_set = set(choice)
+        results.append(choice_set)
+
+    # remove superset and duplicates
+    unique = []
+    for s in results:
+        if any(s > other for other in results if s is not other):
+            continue
+        if s not in unique:
+            unique.append(s)
+
+    return unique
+
+
+def cc1_args_for_features(features: set[str]) -> str:
+    return " ".join("-target-feature +" + s for s in sorted(features))
+
+
+def sanitise_guard(s: str) -> str:
+    """Rewrite guard strings in a form more suitable for file naming."""
+    replacements = {
+        ",": "_AND_",
+        "|": "_OR_",
+        "(": "_LP_",
+        ")": "_RP_",
+    }
+    for k, v in replacements.items():
+        s = s.replace(k, v)
+
+    # Collapse multiple underscores
+    s = re.sub(r"_+", "_", s)
+    return s.strip("_")
+
+
+def make_filename(prefix: str, ctx: BuiltinContext, ext: str) -> str:
+    parts = [sanitise_guard(ctx.guard), sanitise_guard(ctx.streaming_guard)]
+    sanitised_guard = "___".join(p for p in parts if p)
+
+    if "streaming-only" in ctx.flags:
+        prefix += "_streaming_only"
+    elif "streaming-compatible" in ctx.flags:
+        prefix += "_streaming_compatible"
+    elif "feature-dependent" in ctx.flags:
+        prefix += "_feature_dependent"
+    else:
+        prefix += "_non_streaming_only"
+
+    return f"{prefix}_{sanitised_guard}{ext}"
+
+
+# --- Code Generation --------------------------------------------------------
+
+
+def emit_streaming_guard_run_lines(ctx: BuiltinContext) -> str:
+    """Emit lit RUN lines that will exercise the relevent Sema diagnistics."""
+    run_prefix = "// RUN: %clang_cc1 %s -fsyntax-only -triple aarch64-none-linux-gnu"
+    out: List[str] = []
+
+    # All RUN lines have SVE and SME enabled
+    guard_features = expand_feature_guard(ctx.guard, ctx.flags, "sme")
+    streaming_guard_features = expand_feature_guard(
+        ctx.streaming_guard, ctx.flags, "sve"
+    )
+
+    if "streaming-only" in ctx.flags:
+        assert not guard_features
+        # Generate RUN lines for features only availble to streaming functions
+        for feats in streaming_guard_features:
+            out.append(
+                f"{run_prefix} {cc1_args_for_features(feats)} -verify=streaming-guard"
+            )
+    elif "streaming-compatible" in ctx.flags:
+        assert not guard_features
+        # NOTE: Streaming compatible builtins don't require SVE.
+        # Generate RUN lines for features available to all functions.
+        for feats in expand_feature_guard(ctx.streaming_guard, ctx.flags):
+            out.append(f"{run_prefix} {cc1_args_for_features(feats)} -verify")
+        out.append("// expected-no-diagnostics")
+    elif "feature-dependent" in ctx.flags:
+        assert guard_features and streaming_guard_features
+        combined_features = expand_feature_guard(
+            ctx.guard + "," + ctx.streaming_guard, ctx.flags
+        )
+
+        # Generate RUN lines for features only availble to normal functions
+        for feats in guard_features:
+            if feats not in combined_features:
+                out.append(f"{run_prefix} {cc1_args_for_features(feats)} -verify=guard")
+
+        # Geneate RUN lines for features only available to streaming functions
+        for feats in streaming_guard_features:
+            if feats not in combined_features:
+                out.append(
+                    f"{run_prefix} {cc1_args_for_features(feats)} -verify=streaming-guard"
+                )
+
+        # Generate RUN lines for features available to all functions
+        for feats in combined_features:
+            out.append(f"{run_prefix} {cc1_args_for_features(feats)} -verify")
+
+        out.append("// expected-no-diagnostics")
+    else:
+        assert not streaming_guard_features
+        # Geneate RUN lines for features only available to normal functions
+        for feats in guard_features:
+            out.append(f"{run_prefix} {cc1_args_for_features(feats)} -verify=guard")
+
+    return "\n".join(out)
+
+
+def emit_streaming_guard_function(
+    ctx: BuiltinContext,
+    var_decls: Sequence[str],
+    calls: Sequence[str],
+    func_name: str,
+    func_type: FunctionType = FunctionType.NORMAL,
+) -> str:
+    """Emit a C function calling all builtins.
+
+    `calls` is a sequence of tuples: (name, call_line)
+    """
+    # Expected Sema diagnostics for invalid usage
+    require_diagnostic = require_streaming_diagnostic = False
+    if "streaming-only" in ctx.flags:
+        if func_type != FunctionType.STREAMING:
+            require_streaming_diagnostic = True
+    elif "streaming-compatible" in ctx.flags:
+        pass  # streaming compatible builtins are always available
+    elif "feature-dependent" in ctx.flags:
+        guard_features = expand_feature_guard(ctx.guard, ctx.flags, "sme")
+        streaming_guard_features = expand_feature_guard(
+            ctx.streaming_guard, ctx.flags, "sve"
+        )
+        combined_features = expand_feature_guard(
+            ctx.guard + "," + ctx.streaming_guard, ctx.flags
+        )
+
+        if func_type != FunctionType.NORMAL:
+            if any(feats not in combined_features for feats in guard_features):
+                require_diagnostic = True
+        if func_type != FunctionType.STREAMING:
+            if any(
+                feats not in combined_features for feats in streaming_guard_features
+            ):
+                require_streaming_diagnostic = True
+    else:
+        if func_type != FunctionType.NORMAL:
+            require_diagnostic = True
+
+    out: List[str] = []
+
+    # Emit test function declaration
+    attr: list[str] = []
+    if func_type == FunctionType.STREAMING:
+        attr.append("__arm_streaming")
+    elif func_type == FunctionType.STREAMING_COMPATIBLE:
+        attr.append("__arm_streaming_compatible")
+
+    if "requires-za" in ctx.flags:
+        attr.append('__arm_inout("za")')
+    if "requires-zt" in ctx.flags:
+        attr.append('__arm_inout("zt0")')
+    out.append(f"void {func_name}(void) " + " ".join(attr) + "{")
+
+    # Emit variable declarations
+    for v in var_decls:
+        out.append(f"  {v}")
+    if var_decls:
+        out.append("")
+
+    # Emit calls
+    for call in calls:
+        if require_diagnostic and require_streaming_diagnostic:
+            out.append(
+                "  // guard-error at +2 {{builtin can only be called from a non-streaming function}}"
+            )
+            out.append(
+                "  // streaming-guard-error at +1 {{builtin can only be called from a streaming function}}"
+            )
+        elif require_diagnostic:
+            out.append(
+                "  // guard-error at +1 {{builtin can only be called from a non-streaming function}}"
+            )
+        elif require_streaming_diagnostic:
+            out.append(
+                "  // streaming-guard-error at +1 {{builtin can only be called from a streaming function}}"
+            )
+        out.append(f"  {call}")
+
+    out.append("}")
+    return "\n".join(out) + "\n"
+
+
+def natural_key(s: str):
+    """Allow sorting akin to "sort -V"""
+    return [int(text) if text.isdigit() else text for text in re.split(r"(\d+)", s)]
+
+
+def build_calls_for_group(builtins: Iterable[str]) -> Tuple[List[str], List[str]]:
+    """From a list of builtin declaration strings, produce:
+    - a sorted list of unique variable declarations
+    - a sorted list of builtin calls
+    """
+    var_decls: List[str] = []
+    seen_types: set[str] = set()
+    calls: List[str] = []
+
+    for decl in builtins:
+        fn, param_types = parse_builtin_declaration(decl)
+
+        arg_names: List[str] = []
+        for i, ptype in enumerate(param_types):
+            vdecl, vname = make_arg_for_type(ptype)
+            if vdecl and vdecl not in seen_types:
+                seen_types.add(vdecl)
+                var_decls.append(vdecl)
+            arg_names.append(vname)
+
+        calls.append(f"{fn}(" + ", ".join(arg_names) + ");")
+
+    # Natural sort (e.g. int8_t before int16_t)
+    calls.sort(key=lambda t: natural_key(t))
----------------
huntergr-arm wrote:

```suggestion
    calls.sort(key=natural_key)
```

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


More information about the cfe-commits mailing list