I shortly talked about this proposal as comment here, but I think I can refine it more.
Goal
Most general programming languages store their local variables on stack (lets not think of optimization for now). however in MLIR, there are no such dialect that natively supports it.
If someone wants to build general purpose programming language using MLIR, this would be very helpful.
For example,
int main() {
int a = 0; // stored on stack
int b = 1; // stored on stack
return a + b // loaded from stack, and added
}
New types to introduce
!stack.lv
type (left-value)
(represented as lv
This type represents values that are pushed to stack. by default, values that arenāt on the stack is called r-value
. If this is pushed onto stack, itās typed as l-value.
!stack.ref
type
This type represents reference to l-value types. This is useful if one wants to represent value that āreferencesā the other value. For example in Rust,
let mut a = 0;
let mut b = &mut a; //borrows (references a)
if a is updated, b is updated as well. In this case, b points to stack address of āaā internally. The type of ābā would be ref<lv<type>>
New operations
-
%res = stack.push %value
This operation pushes value onto the stack, converting r-value to l-value -
%ref = stack.reference %value
This operation creates reference of the l-value%value
. Note that%value
must be!stack.lv
type.
3.%value = stack.deepCopy %lv
This operation copies l-value and creates r-value. For trivially copyable types (such as scalar types), this is simple memcpy. For mermef or tensors, this would be creation of new instance, independent from the %lv
. (Should be internally translated to something like memref.copy or tensor.copy)
%value = stack.shallowCopy %lv
This operation moves l-value and creates r-value. For trivially copyable types, this is same asstack.deepCopy
. But for types with internal indirections, (such as tensor or memref types) This operation only shallow copies enclosing structure, without copying the actual data.
Here I only stated very basic set of operations
Above example given in C++ may be converted as (Of course, this can be optimized to remove stack operations eventually if possible)
%c0 = arith.constant 0 : i32
%a = stack.push %c0 : (i32) -> !stack.lv<i32>
%c1 = arith.constant 1 : i32
%b = stack.push %c1 : (i32) -> !stack.lv<i32>
%a_rv = stack.copy %a : (!stack.lv<i32>) -> i32
%b_rv = stack.copy %b : (!stack.lv<i32>) -> i32
%rtn = arith.add %a_rv, %b_rv : i32
func.return %rtn : i32
This rust example can be represented something like
let mut a = 0;
let mut b = &mut a; //borrows (references a)
%c0 = arith.constant 0 : i32
%a = stack.push %c0 { mutable : true} : (i32) -> !stack.lv<i32>
%b = stack.reference %a : (!stack.lv<i32>) -> !stack.ref<!stack.lv<i32>>
I accepted this idea, and has already built some kind of general-purpose language for optimizing hpc workloads. I found above functionalities useful for doing it, and thought it would be great if MLIR natively supported it.
If you like this idea, there are a lot more to discuss about. we can track lifetimes of tensor or memrefs, and implement some crucial language features that general-purpose languages often provide.
Iād like to receive some feedback and questions about this proposal