Why does MLIR use ValueRange instead of ArrayRef? (original) (raw)

Hi everyone,

I’m trying to understand the difference between ValueRange and ArrayRef<Value> in MLIR. Could someone explain:

  1. Why does MLIR introduce ValueRange instead of using ArrayRef<Value>?
  2. What are the key differences between them in terms of usage and behavior?

Thanks!

The intention was that this would make it possible to work with arrays of operands as well as an array of Value. Operand has a bunch of other state that makes it not a simple pointer.

To expand a bit on Chris’ answer, this is an optimization. If we wrote an API as accepting an ArrayRef<Value>, then you would always need to be able to provide a compact in-memory sequence of value to pass as an array to this API.
However in MLIR you don’t necessarily have an array of Value objects. In the IR these don’t exist, because Value (as well as the derived convenient BlockArgument and OpResult) are just thin pointer-like wrapper. They are meant to be ephemeral and constructed to hold a single data member: a pointer to the Value implementation.

These slides dive into some implementation aspects. Looking at the slides about Operation storage: Results show that an operation returning multiple Value are storing them consecutively in memory, but with different actual implementation (and size!) depending on the position of the result (as a size optimization).
An ArrayRef<Value> would actually need to store pointers to each of the result: it would have to be constructed when needed, which is costly. Instead the ValueRange is “emulating” this by taking a reference to the complex storage of OpResults and providing a way to iterate through it with a Value API.

Similarly the OpOperand (the operands of an operation) is actually a complex array containing references to linked list entries that points to either a BlockOperand or an OpResult implementation: see the drawing at the end of this documentation: Understanding the IR Structure - MLIR
The ValueRange is again hiding this complexity through a uniform API that allows to access it as a “range of Value” even if under the hood this would be dereferencing through one or two pointers to get the value implementation and constructing a Value object on the fly.