Thank you for the proposal @MuyaoXiao, and excuse the delay taking a look at this. I’m no expert on the MLIR dataflow analysis framework, so do let me know if I’m misunderstanding or missing anything. I’ve been investigating this for a bit to hopefully help with some ideas.
From what I can tell, it seems like you’re right about the need for implementing the RegionBranchOpInterface if we want to enable dataflow analyses across some operation’s region boundaries without resetting to the pessimistic defaults.
There are some characteristics of omp.target that probably make it not very straightforward to handle, though. The issues there are: i) target regions run asynchronously if they have the nowait attribute, in which case an analysis that assumes that “exiting the omp.target region means immediately triggering all the map-exit semantics” would be wrong; and ii) their region executes in a separate device context independent of any analysis data that might have been gathered for the host.
What I’m thinking is that, in addition to implementing a simple RegionBranchOpInterface for omp.target representing its region as if executing inline with only host_eval values passed along as entry successor region operands (the rest of entry block arguments represent target region private copies there, not outside aliases), it probably would make sense to introduce the MemoryEffectOpInterface and the RecursiveMemoryEffects trait for it to model the memory transfers too. The former would be the one responsible for modeling map- and firstprivate-related reads and writes, and possibly forcing a pessimistic read+write effect on every mapped value to prevent invalid order-based optimizations in the nowait case, and the latter would make sure that e.g. map-less target regions wouldn’t get DCE’d when they had side-effecting device code in them. The SideEffects::EffectInstance stage could probably be set to tag region entry and exit memory effects separately, if your analysis needs to handle them separately.
I think that this should let a forward dense dataflow analysis go though omp.target while keeping track of host memory reads/writes (and also keep the device-side analysis of each target region isolated), and be pessimistically handled whenever nowait is present. Your analysis could query these data movements by looking at the memory effects of the relevant operation.
omp.target_data is simpler in this case, as it only contains host code and it doesn’t take a nowait clause or host_eval operands. The RegionBranchOpInterface and MemoryEffectOpInterface would end up being simplified versions of those for omp.target. I don’t know if perhaps it would make sense in your case for ops such as omp.target_enter_data to also properly state their memory effects.
I think we’re currently quite conservative in the omp dialect in general with regards to memory effects (relatively few operations actually define any), and this seems like a situation where that is shielding us from potential useful optimizations / analyses. Other dialect ops will still trip these up and be excessively conservative, but I guess we have to start somewhere with properly defining memory effects. However, we need to be really careful while doing it, because the current sub-optimal situation is preferable to a slightly wrong modeling of memory effects causing illegal transformations.
Do let me know, though, if you had a simpler approach in mind or if perhaps you think going into the full memory effect modeling path for this isn’t a good idea.