[gen] Rework AArch64 access atoms - #1956
Conversation
a069c9e to
15dfd9a
Compare
|
This PR adds thousands of files (mostly litmus tests as far as I can see). Were they added by mistake or was it intentional? |
I add a thorough test suite on diyone so we test all existing compilation paths (at least I am aware of). This includes valid and invalid it will read the first line to run the |
15dfd9a to
2fd4cfd
Compare
I’m not sure just reducing some of the invalid paths is enough. Cram tests should ideally be a curated and meaningful set of examples, each acting as representative of an input-output behaviour of interest, or a past bug. “Meaningful” is key: I see very little utility in having multiple golden tests exercising the same or very similar code paths. It just adds noise to the review process. For example, the two Moreover, cram tests are only useful if they can be reviewed, to verify that their “expected output” is actually correct and indeed expected. However, this PR adds 3000+ tests, and I don’t think anyone could reasonably be asked to review all of them or even a tenth. So while I agree these cram tests are testing something, I’m not convinced they are necessarily testing for correct behaviour. I’d suggest first identifying the core code paths and user-visible behaviours affected by this PR that need to be tested. Then we should think about whether those tests are better expressed as focused OCaml tests (e.g. using unit testing or property testing), or if they are actually better suited for broad end-to-end cram testing. If the latter, then I think we should then aim for one or maybe two curated cram tests per path.
It would be helpful to run benchmarks to better quantify this. In general, whether they are "too" expensive or not also depends on their utility. A large test suite of cram tests accumulated over the years to capture meaningful behaviours might be time-expensive to run, but worth keeping nonetheless.
I agree, but I think adding thousands of tests in one go will be difficult to audit in any case, whether they are put in a single file or multiple files. |
Let me go through all the test cases again. I think to ensure the semantics equivalent we need to have test to cover behaviour regarding (1) one annotation and (2) pair of annotations in combine with (a) read or (b) write. Those should cover all the path regarding annotation. Current it exhausted all the combination but I think I can present the test based on group of annotation and only add a few test cases for a group. |
Can you elaborate on why do you think this is an improvement over the previous representation? This description shows how At a glance, the new representation looks less precise than the old in some cases. For example, the old |
First to clarify the old type was technically defined as : where there was a second projection MachMixed.t option used for mixed size operation.
We could make it more restrictive by designing type that rules out acq-rel semantics to tag, ifetch and neon. However, I decide to make it slightly simpler of two fields in a structure and rule out same invalid cases. There was a similar |
2fd4cfd to
95c1aac
Compare
One thing I find particularly confusing in this representation is that it suggest the incorrect equivalence "acquire access <--> Have you considered replacing this line: | Plain of capa_opt | Acq of capa_opt | AcqPc of capa_opt | Rel of capa_optwith a single constructor like so: type acq_rel = Plain | Acq | AcqPc | Rel
type generic_access = {
acq_rel : acq_rel;
capa : capa_opt;
}
type atom_acc =
| Generic of generic_access
| ...Perhaps this might help in making things a bit more symmetrical/uniform.
I'm all for simple types where possible, however the simplification proposed here impacts the type-safety and general robustness of the code, so I think its tradeoffs should be highlighted and weighed against alternative solutions. I'm also not entirely sure the proposed approach makes the implementation simpler overall, at least by looking at the diff. I think it would be useful to see some spelled-out concrete examples of code that was positively impacted (in terms of simplicity, readability, etc.) by the change proposed in this PR. In other words, I'm trying to understand whether the goal of this PR is just to make the
Right, but the version of |
Can you elaborate a bit further here ? Here I think there is still different between mixed-size acq,
Yes and No. If we are talking about a single annotation, the actually complexity is in the |
Sure. Indeed I wasn't proposing a way to completely unify every instance of access order. Rather, that was a (sketched and incomplete) idea to address the specific asymmetry I was concerned with, which sees the same type atom_acc =
| AccessType1 of acc_type_1_order
| AccessType2 of acc_type_2_order
| ...There may be more opportunities for clean-up in addition to just adding a |
I reduce the test cases to a similar size as before now but more importantly, I hope it becomes clearer and more representative. First the old way rely only To ensure this refactor on I particularly want this file is a valid
In short, the test are generated BEFORE any refactoring so it ensure any of the future commits does not change the existing expected behaviours |
it was not I think we can do The following up question is |
Correct, so let me be more specific: it wasn't “valid by construction” in 100% of cases. But it was at least partly so, to a good extent.
I feel like I would still find this confusing, as I would ask myself in what way is |
I think I am trying to avoid splitting some existing typing here for example |
TiberiuBucur
left a comment
There was a problem hiding this comment.
I have not had a look at individual tests yet. Will get back to that tomorrow.
|
When it's not possible to ensure a type that's valid by construction, one solution I've found to work well is to make the type module-private. While this is a compromise, I've found it to work well in several occasions. Making the type module-private excludes code that is not from the module to construct values of the type. This means it's easy to create constructor functions that tests properties of the components before returning valid values for the type. This pattern also allows users to pattern-match the type as well. This means we have gained a valuable property, construction is now localised into a few (if not one) functions. It's now easy to verify the properties using unit-testing and property-based testing (if needed), and it's also nicer to review and believe that the code is correct and nothing slipped through the cracks. And if it did, it's also easier to fix. Here's an example where a record was needed and the implementing types ( In this commit the module is used, showing pattern-matching to extract values from the record: xapi-project/xen-api@6046cce Another cases where bounded floats were needed: xapi-project/xen-api@76eed66#diff-f8c1c61e079650079206ad08e503aee1a90065d39872f330c7bc37b2e8bee7d4R23 |
Thanks for the suggestion ! I will read it. I have chatted with @fsestini offline, as we will tighter the type definition so it is closer to "correct" by construction here. |
2e6b9b1 to
4734583
Compare
There was a problem hiding this comment.
I noticed this test is allowed, whilst the "non-strong" cachesync version with an ISB is forbidden. That is presumably because in the memory model Instr-obs is written in terms of DC.CVAU instructions. @artkhyzha is this on purpose?
4274087 to
36541be
Compare
Print edge annotations as separate tokens in Orig metadata so the description can be passed back to diyone7. Keep instruction-fetch annotations in composite edge form because standalone I is invalid.
cc8b957 to
625656f
Compare
This PR reworks AArch64 generator atoms into a structured representation that separates the kind of access from its ordering semantics.
Problems with the old representation
The old representation was:
Ordinary and Morello accesses shared the same constructors
Plain,Acq,AcqPc, andRelrepresented both ordinary accesses and Morello capability accesses. Thecapa_optpayload distinguished them:Consequently, code not relavant to morello had to inspect an option nested inside the ordering constructor. The common ordinary path always carried
None, while Morello-specific behavior was encoded indirectly asSome Capability.This also made constructor names incomplete descriptions of their values. For example,
Plaindid not by itself mean an ordinary plain access, andAcqdid not by itself identify the accessed data kind.Mixed size was represented as an independent optional dimension
Every atom was paired with a
MachMixed.t option, even though only ordinary and atomic accesses meaningfully support mixed sizes. For example:represented an acquire mixed-size access such as
A.b0.For non-mixed accesses, the second component was always
None. More importantly, the type could also express combinations that have no valid meaning, such as attaching a mixed size toTag,Pte,Neon,Instr, or a Morello capability access. These combinations had to be rejected or ignored later by convention. Code handling an atom therefore had to combine two independent matches: one overatom_accand another overMachMixed.t option. Ordinary and mixed-size versions of the same operation often appeared in separate branches even when their behavior was otherwise identical.PTE ordering semantics were encoded in constructor names
The old PTE representation was:
This mixed the PTE operation with its ordering semantics:
Read,ReadAcq, andReadAcqPcrepresented the same read operation with plain, acquire, and acquire-PC ordering.SetandSetRelrepresented the same PTE-field update with plain and release ordering.ReadHAAcqandReadHAAcqPcrepeated the acquire distinction for the specialHAread case.As a result, changing only the ordering required changing constructors. For example,
PteV1andLwere parsed as:Their merge produced
PteV1Lby constructing:The release property moved from a general
Relconstructor into the PTE-specificSetRelconstructor. The same semantic operation was therefore represented differently depending on the access kind.PteHAwas especially subtle. Its plain form shared theSetrepresentation for parsing reasons, while its acquire forms used dedicated read constructors. Code processing PTE atoms consequently needed special cases to determine whetherHAdescribed a read, a field update, or an ordered read.Validity depended heavily on conventions outside the type
The old product type had many combinations that compilation could not use such as:
Tag,Pte,Neon,Pair, orInstratom;MachMixed.t;These states were handled through repeated pattern matching, assertions, or later validation. Adding a constructor or semantic case required auditing printing, comparison, merging, enumeration, direction checking, machine-feature detection, and compilation code for both tuple components.
Atom operations duplicated representation knowledge
Printing, comparison, annotation merging, mixed-size handling, RMW validation, and compilation all needed to understand the same encoding rules:
NoneversusSome Capability;NoneversusSome MachMixed.t;HA;None.This produced large matches with similar branches and made it easy for two operations to interpret the same atom differently. It also made otherwise local changes to an access kind affect code throughout the generator.
New representation
Ordering is now represented explicitly:
PTE operations carry an ordering appropriate to their direction:
The top-level atom identifies the access kind directly:
This gives each semantic dimension one consistent location:
access_read,access_writeand thenaccess_orderfor release-acquire order, which are used consistent through ordinary, vmsa, mixed-size and Morello;MachMixed.tappears only in constructors that support a mixed size;atom_rwcontinues to represent the read/write ordering of atomic operations;For example:
The
PteV1plusLmerge now changes the ordering value while retaining the PTE operation and field set:Similarly,
PteHA,PteHAA, andPteHAQare represented as one operation with different read orders:Migration strategy
The migration is split into small commits. The structured representation is introduced alongside compatibility conversion, and individual consumers are then migrated in compilation order. Printing, comparison, enumeration, annotation merging, mixed-size handling, dependency compilation, RMW compilation, PTE compilation, vector compilation, and architecture metadata are moved incrementally before structured atoms become canonical.
Each commit is buildable, and the regression tests were generated before the refactoring so that the migration can be checked against the existing behavior at every stage.
Test suite
To ensure we preserve the existing behaviour. We introduce a new test suite to replace the old
diycross7+herd7+ checking log method. The old method is unnecessary viaherd7also thediycross7inputs mixes various of testing targets. The new test suite includes individual test for edge and annotation. Each test file is a valid litmus test. Its first line is a comment containing thediyone7command that reproduces the remainder of the file.The suite contains eight AArch64 directories:
AArch64— Baseline coverage forPo,Dp*, atomic operations, and representativeDMB/DSBfences. Annotations are tested around compilation-distinct edges such asPosRW,LxSx,Dp*,Amo.Cas, andAmo.Swp.AArch64.ifetch— Coverage forCacheSync*,DC.*, andIC.*edges, together with instruction annotations on representative dependency and atomic paths.AArch64.mixed— Mixed-size annotation coverage using representative sizes and offsets. It includes same-size and different-size pairs and exercises theCU,MixedDisjoint, andMixedStrictOverlapvariants.AArch64.morello— Morello annotation coverage aroundPosRW,LxSx, representative dependencies,Amo.Cas, andAmo.Swp.AArch64.MTE— Memory-tagging coverage for theTannotation, includingasyncandstoreonly. Multi-process cases exercise fault generation.AArch64.vmsa— VMSA PTE annotation coverage. Representative annotations exercise dependency and atomic paths, while two-PTE combinations check compatibility and generated PTE values.AArch64.vector— Neon, SVE, and SME annotation coverage around representative plain, dependency, load-exclusive/store-exclusive, and atomic paths.AArch64.store— Focused coverage for theStoreedge in each applicable direction.These tests were generated before the refactoring began and provide a regression baseline to ensure that subsequent commits preserve existing behaviour.