diff options
Diffstat (limited to 'clang/utils')
-rw-r--r-- | clang/utils/TableGen/SveEmitter.cpp | 110 | ||||
-rw-r--r-- | clang/utils/TableGen/TableGen.cpp | 12 | ||||
-rw-r--r-- | clang/utils/TableGen/TableGenBackends.h | 4 | ||||
-rwxr-xr-x | clang/utils/aarch64_builtins_test_generator.py | 541 |
4 files changed, 664 insertions, 3 deletions
diff --git a/clang/utils/TableGen/SveEmitter.cpp b/clang/utils/TableGen/SveEmitter.cpp index af2dcf6..553c7a3 100644 --- a/clang/utils/TableGen/SveEmitter.cpp +++ b/clang/utils/TableGen/SveEmitter.cpp @@ -243,7 +243,9 @@ public: /// Return the name, mangled with type information. The name is mangled for /// ClassS, so will add type suffixes such as _u32/_s32. - std::string getMangledName() const { return mangleName(ClassS); } + std::string getMangledName(ClassKind CK = ClassS) const { + return mangleName(CK); + } /// As above, but mangles the LLVM name instead. std::string getMangledLLVMName() const { return mangleLLVMName(); } @@ -309,6 +311,7 @@ private: StringMap<uint64_t> FlagTypes; StringMap<uint64_t> MergeTypes; StringMap<uint64_t> ImmCheckTypes; + std::vector<llvm::StringRef> ImmCheckTypeNames; public: SVEEmitter(const RecordKeeper &R) : Records(R) { @@ -320,8 +323,15 @@ public: FlagTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); for (auto *RV : Records.getAllDerivedDefinitions("MergeType")) MergeTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); - for (auto *RV : Records.getAllDerivedDefinitions("ImmCheckType")) - ImmCheckTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); + for (auto *RV : Records.getAllDerivedDefinitions("ImmCheckType")) { + auto [it, inserted] = ImmCheckTypes.try_emplace( + RV->getNameInitAsString(), RV->getValueAsInt("Value")); + if (!inserted) + llvm_unreachable("Duplicate imm check"); + if ((size_t)it->second >= ImmCheckTypeNames.size()) + ImmCheckTypeNames.resize((size_t)it->second + 1); + ImmCheckTypeNames[it->second] = it->first(); + } } /// Returns the enum value for the immcheck type @@ -340,6 +350,13 @@ public: llvm_unreachable("Unsupported flag"); } + /// Returns the name for the immcheck type + StringRef getImmCheckForEnumValue(unsigned Id) { + if ((size_t)Id < ImmCheckTypeNames.size()) + return ImmCheckTypeNames[Id]; + llvm_unreachable("Unsupported imm check"); + } + // Returns the SVETypeFlags for a given value and mask. uint64_t encodeFlag(uint64_t V, StringRef MaskName) const { auto It = FlagTypes.find(MaskName); @@ -389,6 +406,9 @@ public: /// Emit all the __builtin prototypes and code needed by Sema. void createBuiltins(raw_ostream &o); + /// Emit all the __builtin prototypes in JSON format. + void createBuiltinsJSON(raw_ostream &o); + /// Emit all the information needed to map builtin -> LLVM IR intrinsic. void createCodeGenMap(raw_ostream &o); @@ -1552,6 +1572,82 @@ void SVEEmitter::createBuiltins(raw_ostream &OS) { OS << "#endif // GET_SVE_BUILTIN_INFOS\n\n"; } +void SVEEmitter::createBuiltinsJSON(raw_ostream &OS) { + SmallVector<std::unique_ptr<Intrinsic>, 128> Defs; + std::vector<const Record *> RV = Records.getAllDerivedDefinitions("Inst"); + for (auto *R : RV) + createIntrinsic(R, Defs); + + OS << "[\n"; + bool FirstDef = true; + + for (auto &Def : Defs) { + std::vector<std::string> Flags; + + if (Def->isFlagSet(getEnumValueForFlag("IsStreaming"))) + Flags.push_back("streaming-only"); + else if (Def->isFlagSet(getEnumValueForFlag("IsStreamingCompatible"))) + Flags.push_back("streaming-compatible"); + else if (Def->isFlagSet(getEnumValueForFlag("VerifyRuntimeMode"))) + Flags.push_back("feature-dependent"); + + if (Def->isFlagSet(getEnumValueForFlag("IsInZA")) || + Def->isFlagSet(getEnumValueForFlag("IsOutZA")) || + Def->isFlagSet(getEnumValueForFlag("IsInOutZA"))) + Flags.push_back("requires-za"); + + if (Def->isFlagSet(getEnumValueForFlag("IsInZT0")) || + Def->isFlagSet(getEnumValueForFlag("IsOutZT0")) || + Def->isFlagSet(getEnumValueForFlag("IsInOutZT0"))) + Flags.push_back("requires-zt"); + + if (!FirstDef) + OS << ",\n"; + + OS << "{ "; + OS << "\"guard\": \"" << Def->getSVEGuard() << "\","; + OS << "\"streaming_guard\": \"" << Def->getSMEGuard() << "\","; + OS << "\"flags\": \""; + + for (size_t I = 0; I < Flags.size(); ++I) { + if (I != 0) + OS << ','; + OS << Flags[I]; + } + + OS << "\",\"builtin\": \""; + + std::string BuiltinName = Def->getMangledName(Def->getClassKind()); + + OS << Def->getReturnType().str() << " " << BuiltinName << "("; + for (unsigned I = 0; I < Def->getTypes().size() - 1; ++I) { + if (I != 0) + OS << ", "; + + SVEType ParamType = Def->getParamType(I); + + // These are ImmCheck'd but their type names are sufficiently clear. + if (ParamType.isPredicatePattern() || ParamType.isPrefetchOp()) { + OS << ParamType.str(); + continue; + } + + // Pass ImmCheck information by pretending it's a type. + auto Iter = llvm::find_if(Def->getImmChecks(), [I](const auto &Chk) { + return (unsigned)Chk.getImmArgIdx() == I; + }); + if (Iter != Def->getImmChecks().end()) + OS << getImmCheckForEnumValue(Iter->getKind()); + else + OS << ParamType.str(); + } + OS << ");\" }"; + FirstDef = false; + } + + OS << "\n]\n"; +} + void SVEEmitter::createCodeGenMap(raw_ostream &OS) { std::vector<const Record *> RV = Records.getAllDerivedDefinitions("Inst"); SmallVector<std::unique_ptr<Intrinsic>, 128> Defs; @@ -1937,6 +2033,10 @@ void EmitSveBuiltins(const RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createBuiltins(OS); } +void EmitSveBuiltinsJSON(const RecordKeeper &Records, raw_ostream &OS) { + SVEEmitter(Records).createBuiltinsJSON(OS); +} + void EmitSveBuiltinCG(const RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createCodeGenMap(OS); } @@ -1965,6 +2065,10 @@ void EmitSmeBuiltins(const RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createSMEBuiltins(OS); } +void EmitSmeBuiltinsJSON(const RecordKeeper &Records, raw_ostream &OS) { + SVEEmitter(Records).createBuiltinsJSON(OS); +} + void EmitSmeBuiltinCG(const RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createSMECodeGenMap(OS); } diff --git a/clang/utils/TableGen/TableGen.cpp b/clang/utils/TableGen/TableGen.cpp index df14955..866040d 100644 --- a/clang/utils/TableGen/TableGen.cpp +++ b/clang/utils/TableGen/TableGen.cpp @@ -89,12 +89,14 @@ enum ActionType { GenArmMveBuiltinAliases, GenArmSveHeader, GenArmSveBuiltins, + GenArmSveBuiltinsJSON, GenArmSveBuiltinCG, GenArmSveTypeFlags, GenArmSveRangeChecks, GenArmSveStreamingAttrs, GenArmSmeHeader, GenArmSmeBuiltins, + GenArmSmeBuiltinsJSON, GenArmSmeBuiltinCG, GenArmSmeRangeChecks, GenArmSmeStreamingAttrs, @@ -266,6 +268,8 @@ cl::opt<ActionType> Action( "Generate arm_sve.h for clang"), clEnumValN(GenArmSveBuiltins, "gen-arm-sve-builtins", "Generate arm_sve_builtins.inc for clang"), + clEnumValN(GenArmSveBuiltinsJSON, "gen-arm-sve-builtins-json", + "Generate arm_sve_buitins.json"), clEnumValN(GenArmSveBuiltinCG, "gen-arm-sve-builtin-codegen", "Generate arm_sve_builtin_cg_map.inc for clang"), clEnumValN(GenArmSveTypeFlags, "gen-arm-sve-typeflags", @@ -278,6 +282,8 @@ cl::opt<ActionType> Action( "Generate arm_sme.h for clang"), clEnumValN(GenArmSmeBuiltins, "gen-arm-sme-builtins", "Generate arm_sme_builtins.inc for clang"), + clEnumValN(GenArmSmeBuiltinsJSON, "gen-arm-sme-builtins-json", + "Generate arm_sme_buitins.json"), clEnumValN(GenArmSmeBuiltinCG, "gen-arm-sme-builtin-codegen", "Generate arm_sme_builtin_cg_map.inc for clang"), clEnumValN(GenArmSmeRangeChecks, "gen-arm-sme-sema-rangechecks", @@ -551,6 +557,9 @@ bool ClangTableGenMain(raw_ostream &OS, const RecordKeeper &Records) { case GenArmSveBuiltins: EmitSveBuiltins(Records, OS); break; + case GenArmSveBuiltinsJSON: + EmitSveBuiltinsJSON(Records, OS); + break; case GenArmSveBuiltinCG: EmitSveBuiltinCG(Records, OS); break; @@ -569,6 +578,9 @@ bool ClangTableGenMain(raw_ostream &OS, const RecordKeeper &Records) { case GenArmSmeBuiltins: EmitSmeBuiltins(Records, OS); break; + case GenArmSmeBuiltinsJSON: + EmitSmeBuiltinsJSON(Records, OS); + break; case GenArmSmeBuiltinCG: EmitSmeBuiltinCG(Records, OS); break; diff --git a/clang/utils/TableGen/TableGenBackends.h b/clang/utils/TableGen/TableGenBackends.h index e017571..fa49dcd 100644 --- a/clang/utils/TableGen/TableGenBackends.h +++ b/clang/utils/TableGen/TableGenBackends.h @@ -140,6 +140,8 @@ void EmitImmCheckTypes(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveHeader(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveBuiltins(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitSveBuiltinsJSON(const llvm::RecordKeeper &Records, + llvm::raw_ostream &OS); void EmitSveBuiltinCG(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveTypeFlags(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveRangeChecks(const llvm::RecordKeeper &Records, @@ -149,6 +151,8 @@ void EmitSveStreamingAttrs(const llvm::RecordKeeper &Records, void EmitSmeHeader(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSmeBuiltins(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitSmeBuiltinsJSON(const llvm::RecordKeeper &Records, + llvm::raw_ostream &OS); void EmitSmeBuiltinCG(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSmeRangeChecks(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); diff --git a/clang/utils/aarch64_builtins_test_generator.py b/clang/utils/aarch64_builtins_test_generator.py new file mode 100755 index 0000000..886d6be --- /dev/null +++ b/clang/utils/aarch64_builtins_test_generator.py @@ -0,0 +1,541 @@ +#!/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. +@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] + + if ty.startswith("ImmCheck") or ty.startswith("enum "): + print(f"Failed to parse potential literal type: {ty}", file=sys.stderr) + + 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) + + 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 relevant 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 available 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 available 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@+2 {{builtin can only be called from a non-streaming function}}" + ) + out.append( + " // streaming-guard-error@+1 {{builtin can only be called from a streaming function}}" + ) + elif require_diagnostic: + out.append( + " // guard-error@+1 {{builtin can only be called from a non-streaming function}}" + ) + elif require_streaming_diagnostic: + out.append( + " // streaming-guard-error@+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=natural_key) + var_decls.sort(key=natural_key) + + return var_decls, calls + + +def gen_streaming_guard_tests(mode: Mode, json_path: Path, out_dir: Path) -> None: + """Generate a set of Clang Sema test files to ensure SVE/SME builtins are + callable based on the function type, or the required diagnostic is emitted. + """ + try: + data = json.loads(json_path.read_text()) + except json.JSONDecodeError as e: + print(f"Failed to parse JSON {json_path}: {e}", file=sys.stderr) + return + + # Group by (guard, streaming_guard) + by_guard: Dict[BuiltinContext, List[str]] = defaultdict(list) + for obj in data: + by_guard[BuiltinContext.from_json(obj)].append(obj["builtin"]) + + # For each guard pair, emit 3 functions + for builtin_ctx, builtin_decls in by_guard.items(): + var_decls, calls = build_calls_for_group(builtin_decls) + + out_parts: List[str] = [] + out_parts.append( + "// NOTE: File has been autogenerated by utils/aarch64_builtins_test_generator.py" + ) + out_parts.append(emit_streaming_guard_run_lines(builtin_ctx)) + out_parts.append("") + out_parts.append("// REQUIRES: aarch64-registered-target") + out_parts.append("") + out_parts.append(f"#include <arm_{mode.value}.h>") + out_parts.append("") + out_parts.append(str(builtin_ctx)) + out_parts.append("") + out_parts.append( + emit_streaming_guard_function(builtin_ctx, var_decls, calls, "test") + ) + out_parts.append( + emit_streaming_guard_function( + builtin_ctx, var_decls, calls, "test_streaming", FunctionType.STREAMING + ) + ) + out_parts.append( + emit_streaming_guard_function( + builtin_ctx, + var_decls, + calls, + "test_streaming_compatible", + FunctionType.STREAMING_COMPATIBLE, + ) + ) + + output = "\n".join(out_parts).rstrip() + "\n" + + if out_dir: + out_dir.mkdir(parents=True, exist_ok=True) + filename = make_filename(f"arm_{mode.value}", builtin_ctx, ".c") + (out_dir / filename).write_text(output) + else: + print(output) + + return + + +# --- Main ------------------------------------------------------------------- + + +def existing_file(path: str) -> Path: + p = Path(path) + if not p.is_file(): + raise argparse.ArgumentTypeError(f"{p} is not a valid file") + return p + + +def main(argv: Sequence[str] | None = None) -> int: + ap = argparse.ArgumentParser(description="Emit C tests for SVE/SME builtins") + ap.add_argument( + "json", type=existing_file, help="Path to json formatted builtin descriptions" + ) + ap.add_argument( + "--out-dir", type=Path, default=None, help="Output directory (default: stdout)" + ) + ap.add_argument( + "--gen-streaming-guard-tests", + action="store_true", + help="Generate C tests to validate SVE/SME builtin usage base on streaming attribute", + ) + ap.add_argument( + "--gen-target-guard-tests", + action="store_true", + help="Generate C tests to validate SVE/SME builtin usage based on target features", + ) + ap.add_argument( + "--gen-builtin-tests", + action="store_true", + help="Generate C tests to exercise SVE/SME builtins", + ) + ap.add_argument( + "--base-target-feature", + choices=["sve", "sme"], + help="Force builtin source (sve: arm_sve.h, sme: arm_sme.h)", + ) + + args = ap.parse_args(argv) + + # When not forced, try to infer the mode from the input, defaulting to SVE. + if args.base_target_feature: + mode = Mode(args.base_target_feature) + elif args.json and args.json.name == "arm_sme_builtins.json": + mode = Mode.SME + else: + mode = Mode.SVE + + # Generate test file + if args.gen_streaming_guard_tests: + gen_streaming_guard_tests(mode, args.json, args.out_dir) + if args.gen_target_guard_tests: + ap.error("--gen-target-guard-tests not implemented yet!") + if args.gen_builtin_tests: + ap.error("--gen-builtin-tests not implemented yet!") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) |