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

Paul Walker via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 30 08:27:01 PDT 2025


================
@@ -0,0 +1,538 @@
+#!/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)
+
+    param_types: List[str] = []
+    if params:
+        # 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):
+            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)
----------------
paulwalker-arm wrote:

I think that might be more confusing.  There are tests that only use `sve` and/or `sme` where omitting them would look weird.  I could keep them for those cases but then the naming will be inconsistent.  There's also the small possibility that we'll have similar tests for the NEON instructions available in streaming mode, where we'll not want the non-streaming case to imply SVE is available.

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


More information about the cfe-commits mailing list