Why does llvm.addressof exist?

Why does the llvm.addressof op exist?

llvm.mlir.constant seems to be enough for taking the address of a function (importer code). Why doesn’t it work for global objects as well?

It takes an address of any global, which may or may not be a constant. Doing that with llvm.mlir.constant sounds awkward.

Constants were implemented way before globals and we already had std.constant that created constants of function type, so I just reflected that in the LLVM dialect for the purpose of completeness back in the day. Arguably, llvm.mlr.constant should not support taking an address of a function because (a) in LLVM IR, functions are globals and it’s more consistent to treat them similarly to other globals in the LLVM dialect, (b) there is a “take address” semantics that diverges from all the other constants.

On a side note, importer is mostly experimental code with little maintenance, I would not rely on it for any justification on the LLVM dialect design.

Thanks.

I thought all globals in LLVM IR are referenced by address (not just functions)? Example:

extern int x;
int *returnXPtr() {
    return &x;
}
int returnXValue() {
    return x;
}

@x = external dso_local global i32, align 4

define dso_local i32* @_Z10returnXPtrv() #0 !dbg !7 {
  ret i32* @x, !dbg !13
}

define dso_local i32 @_Z12returnXValuev() #0 !dbg !14 {
  %1 = load i32, i32* @x, align 4, !dbg !17
  ret i32 %1, !dbg !18
}

That is, @x refers to the address of x, which is always a constant, even if the value stored at that address is not.

I agree with all of what you wrote, but I don’t understand how you relate this to using llvm.mlir.constant instead of llvm.addressof?

In the LLVM example you’re showing, the main feature is that a global symbol/address is a Value (with its use-list, etc.). llvm.addressof is materialize the global address of the symbol as an SSA Value before using it.

Yes, but it’s still awkward to reflect that as

llvm.mlir.global @x(...) : !llvm.i32
llvm.func @foo() {
  %0 = llvm.mlir.constant @x : !llvm<"i32*"> // wait, doesn't @x has i32 type?
  llvm.store ..., %0 : !llvm<"i32*"> // didn't I just store to a constant?
}

The amount of surprise and special-casing necessary to support this with llvm.mlir.constant largely outweighs the cost of having an extra operation IMO. Otherwise, we would need to define llvm.mlir.constant along the lines of “if ‘value’ is an attribute with an actual value, then it defines a constant value; if ‘value’ is a symbol reference attribute to a symbol defined by either function or global operation, then it defines a pointer to that value; undefined otherwise”…

addressof is explicit about what it does: takes an address of a global, without implying anything about the global. One can do pointer arithmetic on that address, which would be technically correct in C as well.

Arguably, both llvm.mlir.addressof and llvm.mlir.constant materialize an SSA Value out of an attribute. The former accepts SymbolRef attributes, the latter accepts all the other attributes (and I think it should not accept SymbolRef).

That makes sense to me. I agree it’s a good tradeoff. I just found the current state of the tree confusing since I expected the same treatment for function addresses (llvm.addressoffunc?), but found a seemingly-working path using llvm.mlir.constant for funcs.

Yeah, we should clean that up. https://bugs.llvm.org/show_bug.cgi?id=46344.

I think addressof should support functions as well. They are also globals in the LLVM model.