Skip to content

MLIR: alias classes for globals, and alloc-like effect handling - #3191

Open
burakSemih wants to merge 3 commits into
mainfrom
sb/enzyme-alias-globals
Open

burakSemih wants to merge 3 commits into
mainfrom
sb/enzyme-alias-globals

Conversation

@burakSemih

Copy link
Copy Markdown
Collaborator

AliasAnalysis has no transfer function for llvm.mlir.addressof, so the result
lattice stays undefined. An alias query involving the address of a global then
trips

assert(!isUndefined() && !rhs->isUndefined() && "incomplete alias analysis");

in AliasClassLattice::alias, and in a build without assertions compares two
empty class sets and quietly answers NoAlias — the right answer for two
distinct globals, by accident, and the wrong one for two addresses of the same
global.

This gives the address of a global its own alias class. Three commits, each
buildable on its own:

  1. isAliasTransferFullyDescribedByMemoryEffects named only malloc while
    getEffectsForExternalCall already synthesized an Allocate effect for
    calloc and _Znwm too. Name all three in both places.
  2. The fresh-allocation check required an operation to carry exactly one memory
    effect. That fits memref.alloc and llvm.alloca, but rejects an operation
    that allocates and carries an effect on a resource that is not addressable
    memory — a runtime handle, a communication resource — even though such an
    effect says nothing about aliasing. Factored into a helper that ignores
    effects on non-addressable resources. No in-tree operation changes behaviour;
    every allocator currently modelled has a single effect.
  3. The llvm.mlir.addressof transfer function itself, plus
    test/MLIR/AliasAnalysis/globals.mlir.

Keying on the symbol

The class is cached on the symbol operation, not on the addressof result, so
every llvm.mlir.addressof @g in the module lands in the same class. Keying on
the result value instead gives two addresses of one global two distinct classes
and hence NoAlias, which is wrong — and it is the common case rather than a
corner one, since the LLVM importer emits one addressof per use.

Globals get a unique class regardless of linkage

A global is a distinct object even when its definition lives in another module,
so taking its address cannot produce a pointer into a different global.
External linkage does not change that, and folding external globals together
would lose precision for no soundness benefit.

Addresses of functions and of llvm.mlir.alias are treated differently: they
keep the shared entry class when they may be defined elsewhere.

One consequence worth flagging

Because argument classes and global classes are disjoint, the address of a
global is now NoAlias with an unannotated pointer argument. That asserts no
incoming pointer points into a global, which a caller passing &g violates.

I have left it that way deliberately, for two reasons. The analysis already
answered NoAlias here before this patch — via the undefined lattice above — so
this is not a new answer, only a deliberate one where there used to be an
accident. And widening it means putting globals into the entry class, which
costs exactly the global-vs-global precision this patch is for.

The alternative would be to give externally-visible globals both their own class
and the entry class, so they stay distinct from each other but may-alias
incoming pointers. Happy to do that instead if you would rather have the
conservative answer; it is a small change and I would add the test alongside it.
test/MLIR/AliasAnalysis/globals.mlir pins the current behaviour either way.

There is also a TODO in the patch about unnamed_addr: a global marked
unnamed_addr has no meaningful identity and may be merged with an equivalent
constant, so strictly it should not get a unique class. Not handled here.

Testing

test/MLIR/AliasAnalysis/globals.mlir covers two external globals, two internal
globals, mixed linkage, two addresses of the same global within a function and
across two functions, the address of an external function, and global vs.
argument.

Ran the full test/MLIR suite against this branch and against main: 228 pass,
and the one failure (ReverseMode/func_call_write.mlir) fails on main too.
Each of the three commits builds warning-free, and both touched files are clean
under clang-format 16.

Semih Burak added 3 commits September 8, 2026 01:28
…y path

getEffectsForExternalCall synthesizes an Allocate effect for "calloc" and
"_Znwm" as well as for "malloc", but isAliasTransferFullyDescribedByMemoryEffects
named only "malloc", so the two lists disagreed about which calls are fully
described by their memory effects. Name all three in both places.
The alias and points-to transfer functions treated an operation as a fresh
allocation only when it carried exactly one memory effect. That is the right
shape for memref.alloc and llvm.alloca, but it rejects any operation that
allocates and also carries an effect on a resource that is not addressable
memory -- a runtime handle, a communication resource -- even though such an
effect cannot say anything about aliasing.

Factor the test into a helper that ignores effects on non-addressable resources
and requires a single freshly allocated pointer-like result, and use it both in
PointsToPointerAnalysis::visitOperation and in
isAliasTransferFullyDescribedByMemoryEffects. A value-less read effect on a
non-addressable resource likewise no longer forces the conservative "global
read" path.

No in-tree operation changes behaviour: every allocator currently modelled has a
single effect and so takes the same path as before.
AliasAnalysis had no transfer function for llvm.mlir.addressof, so the result
lattice stayed undefined. An alias query involving the address of a global then
tripped the "incomplete alias analysis" assertion in AliasClassLattice::alias,
and in a build without assertions compared two empty class sets and silently
answered NoAlias.

Give the address of a global its own alias class, keyed on the symbol so that
every llvm.mlir.addressof of the same global lands in the same class -- the LLVM
importer emits one addressof per use, so several addresses of one global is the
common case rather than a corner one. Globals get a unique class regardless of
linkage: a global is a distinct object even when it is defined in another
module, so its address cannot point into a different global. Addresses of
functions and of aliases keep the shared entry class when they may be defined
elsewhere.
@burakSemih

Copy link
Copy Markdown
Collaborator Author

Probably @pengmai would be best for reviewing this. Happy for any feedback ;)

Comment on lines +262 to +263
static Value getSingleFreshAllocatedPointerLikeResultIfEffectOnlyAllocLike(
Operation *op, ArrayRef<MemoryEffects::EffectInstance> effects) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two questions regarding the signature of this function:

  1. Can we return a std::optional<Value> rather than a potentially null Value?
  2. Can we extract the effects from the op? This could potentially be called with effects that don't come from the op, unless that is intended.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants