From 106ab41384da1be79a0fce522cf22f66af1ce1e8 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Wed, 12 Aug 2026 10:07:55 +0200 Subject: [PATCH 1/3] ampere1a: add ampere1 as a parent AmpereOneA has every feature of AmpereOne plus sm3/sm4, but both listed the same parents (neoverse_n1, armv8.6a), so archspec concluded an ampere1 binary cannot run on an ampere1a machine. List ampere1 as a parent, like other same-vendor successors (zen2 from zen, m4 from m3). Signed-off-by: Harmen Stoppels --- cpu/microarchitectures.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/microarchitectures.json b/cpu/microarchitectures.json index 82d899f..5b0f3e0 100644 --- a/cpu/microarchitectures.json +++ b/cpu/microarchitectures.json @@ -3884,7 +3884,7 @@ }, "ampere1a": { "from": [ - "neoverse_n1", + "ampere1", "armv8.6a" ], "vendor": "Ampere", From e4b3a01b874534dae8c537378bf630f15c18a453 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Wed, 12 Aug 2026 10:07:55 +0200 Subject: [PATCH 2/3] cannonlake: add x86_64_v4 as a parent Cannon Lake supports AVX-512 F/CD/VL/BW/DQ, so its feature list already covers all of x86_64_v4, but its only parent was skylake and archspec concluded an x86_64_v4 binary cannot run on a cannonlake machine. List x86_64_v4 as a parent, like skylake_avx512 does. Signed-off-by: Harmen Stoppels --- cpu/microarchitectures.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpu/microarchitectures.json b/cpu/microarchitectures.json index 5b0f3e0..37fbfd5 100644 --- a/cpu/microarchitectures.json +++ b/cpu/microarchitectures.json @@ -1238,7 +1238,8 @@ }, "cannonlake": { "from": [ - "skylake" + "skylake", + "x86_64_v4" ], "vendor": "GenuineIntel", "features": [ From 377bf746dd0b346f78d87e3535014dfa1f0e3d18 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Wed, 12 Aug 2026 10:07:55 +0200 Subject: [PATCH 3/3] CI: check that the from DAG is consistent with feature sets Feature inclusion defines a partial order of its own, and the explicit from DAG must agree with it: a descendant must have all features of its ancestors, and within a family a strict feature superset must descend from the subset when the two share a vendor or the subset is a generic level. Cross-vendor lineage is intentionally not required, since portability between vendors is expressed through the generic levels. This check would have caught the ampere1a and cannonlake omissions fixed in the previous commits. Signed-off-by: Harmen Stoppels --- .github/workflows/validation.yml | 3 + tests/check_dag_consistency.py | 96 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tests/check_dag_consistency.py diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index fdd3501..a7b459b 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -34,3 +34,6 @@ jobs: diff cpu/microarchitectures.json <(jq . cpu/microarchitectures.json) diff cpu/cpuid.json <(jq . cpu/cpuid.json) + - name: Check DAG consistency with feature sets + run: | + python3 tests/check_dag_consistency.py diff --git a/tests/check_dag_consistency.py b/tests/check_dag_consistency.py new file mode 100644 index 0000000..d133b77 --- /dev/null +++ b/tests/check_dag_consistency.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# Copyright 2019-2020 Lawrence Livermore National Security, LLC and other +# Archspec Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Check that the explicit ``from`` DAG is consistent with feature set inclusion. + +Every microarchitecture names a set of features, so feature inclusion defines a +partial order of its own. The ``from`` DAG must agree with it in two directions: + +* A descendant must list every feature of its ancestors, up to ``feature_aliases``. +* Within a family, a microarchitecture whose feature set strictly contains + another's must descend from it when both share a vendor, or when the smaller + one is a generic level. Cross-vendor lineage is intentionally not required: + portability between vendors is expressed through the generic levels only. +""" +import itertools +import json +import pathlib +import sys + +JSON_DIR = pathlib.Path(__file__).parent.parent / "cpu" + + +def main() -> int: + with open(JSON_DIR / "microarchitectures.json") as f: + data = json.load(f) + + uarchs = data["microarchitectures"] + aliases = data.get("feature_aliases", {}) + + ancestors = {} + + def transitive_ancestors(name): + if name not in ancestors: + result = set() + for parent in uarchs[name]["from"]: + result.add(parent) + result |= transitive_ancestors(parent) + ancestors[name] = result + return ancestors[name] + + def family(name): + roots = [a for a in transitive_ancestors(name) | {name} if not uarchs[a]["from"]] + assert len(roots) == 1, f"{name} has multiple family roots: {roots}" + return roots[0] + + def closed_features(name): + """The declared features, closed under the alias rules.""" + result = set(uarchs[name]["features"]) + for feature, rule in aliases.items(): + if "any_of" in rule and result & set(rule["any_of"]): + result.add(feature) + if "families" in rule and family(name) in rule["families"]: + result.add(feature) + return result + + features = {name: closed_features(name) for name in uarchs} + violations = [] + + for ancestor in uarchs: + for descendant in uarchs: + if ancestor not in transitive_ancestors(descendant): + continue + missing = features[ancestor] - features[descendant] + if missing: + violations.append( + f"{descendant} descends from {ancestor} but lacks its features: " + f"{', '.join(sorted(missing))}" + ) + + for smaller, larger in itertools.permutations(uarchs, 2): + if family(smaller) != family(larger): + continue + # an entry with no declared features asserts nothing, and alias closure may still + # give it synthetic ones, so it cannot imply an edge + if not uarchs[smaller]["features"]: + continue + if not features[smaller] < features[larger]: + continue + same_vendor = uarchs[smaller]["vendor"] == uarchs[larger]["vendor"] + if not same_vendor and uarchs[smaller]["vendor"] != "generic": + continue + if smaller not in transitive_ancestors(larger): + violations.append( + f"{larger} has every feature of {smaller} and more, but does not descend " + f"from it" + ) + + for violation in violations: + print(f"ERROR: {violation}") + return 1 if violations else 0 + + +if __name__ == "__main__": + sys.exit(main())