OpenMP constructs can define a variable to be a copy of another variable so that each thread executing the construct gets a separate copy and there are no dependencies. This is called privatisation in OpenMP.
We can handle privatisation in two different ways,
- Privatisation clauses can be dissolved to allocas/allocs by the frontend preceding MLIR.
- Advantages:
- Simpler engineering
- Other layers need not be aware of privatisation.
- Disadvantages:
- All frontends would need to handle privatisation clauses themselves
- Some clauses (allocate) specify that the allocation in privatisation has to be performed by the openmp runtime, this would mean insertion of a runtime call by the frontend.
- Privatisation clauses can be represented in the OpenMP MLIR dialect and handled appropriately.
- Advantages:
- Representation in MLIR would mean frontends can leave it to MLIR to handle privatisation
- MLIR dialect will be more representative of OpenMP and can do some checks.
- Disadvantages:
- Additional engineering effort to make another layer aware of privatisation
- Have to handle copying, constructor, destructor etc, differences in C++/Fortran privatisation
Handling in the frontend is fairly obvious, hence will discuss privatisation in the OpenMP dialect. One way to represent a private clause is by having an operand which is of the same type as the original variable. This operand will be an argument to the entry block. Then a transformation pass can perform the privatisation transformation. This is probably straightforward.
Things become a bit more involved when there are constructors, destructors or if the variable is marked as allocatable (in which case it might have to allocated during runtime). This would probably require the following,
- The constructor, destructor functions.
- The operation (call op) to call the constructor/destructor.
- The operation to perform the allocation, deallocation.
I guess (1) can be stored in the type. The pass which performs the transformation can be aware of (2) and (3) but this would restrict the transformation to be performed at that particular dialect (e.g FIR for Fortran and not in LLVM dialect or during translation.
For discussion:
- Should MLIR have a representation for private clauses?
- Is it OK to represent a private clause as an operand which is a basic block argument of the entry block?
- Should it be a transformation pass which performs the privatisation?
- Can this transformation pass be generic or should it sit with the source dialects?
- Is storing constructor, destructor information in the type the right approach?
A simple Fortran example with FIR and OpenMP dialects is given below. We have a fortran program with an OpenMP Parallel loop with the loop index marked as private. Initially the representation will have a private operand. After transformation the operand will be replaced with an alloca.
Fortran Source | MLIR (FIR + OpenMP) | MLIR (After privatisation) |
---|---|---|
|
|
|
MLIR : @ftynse @schweitz @jeanPerier
OpenMP : @jdoerfert @Meinersbur
Team : @clementval @abidmalikwaterloo @kirankumartp @SouraVX