A bunch of things related to my work on LLVM compiler infrastructure
Phabricator profile link: https://reviews.llvm.org/p/NimishMishra/
[flang][OpenMP][omp2012] Type confusion in reduction clause and fortran intrinsic
Crash observed in threadprivatised common block
reduction(min) gives an inexact negative value close to zero
[Merged] [flang][OpenMP] Do not skip privatization of linear variable if it is OmpPreDetermined
[Merged] [flang][OpenMP] Reintroduce TODO for FIR lowering of linear clause
[Merged] [mlir][OpenMP] Reintroduce TODO for translation of linear clause
[Merged] [[flang][OpenMP] Support MLIR lowering of linear clause for omp.wsloop] (llvm/llvm-project#139385)
[Merged] [flang][OpenMP] Fix fir.convert in omp.atomic.update region Fixes issue #138396
[Merged] [flang][OpenMP] Add implicit casts for omp.atomic.capture Fixes issues #138123 and #94177
[Merged] [flang][llvm][OpenMP] Add implicit casts to omp.atomic
[Merged] [mlir][OpenMP] Add __atomic_store to AtomicInfo Fixes issues #113479 and #115652
[Merged] [flang] Rely on global initialization for simpler derived types
[Merged] [mlir][llvm][OpenMP] Hoist __atomic_load alloca Fixed #120724
[Merged] [llvm][OpenMP] Add implicit cast to omp.atomic.read Fixed #112908
[Merged] [mlir][llvm] Translation support for task detach
[Merged] [flang][OpenMP] Add lowering support for task detach
[Merged] [flang][mlir][llvm][OpenMP] Add lowering and translation support for mergeable clause on task
[Merged] [llvm][OpenMP][NFC] Cleanup AtomicInfo
[Merged] [[flang][OpenMP] Add parsing support for Task detach]
[Merged] [flang][Semantics] Fix updating flags of threadprivate symbols in presence of default clause
[Merged] [flang][OpenMP] Enable lastprivate on simd
[Merged] [llvm][OpenMP] Handle complex types in atomic read
Merged [flang][NFC] Fix failing atomic tests
[Merged] [flang][OpenMP] Error out when CHARACTER type is used in atomic constructs
Merged [flang] Remove failing integration test
[Merged][flang] Fix aarch64 CI failures from #92364
[Merged] [llvm][mlir][flang][OpenMP] Emit __atomic_load and __atomic_compare_exchange libcalls for complex types in atomic update. Relevant issues: #83760 and #75138 and #80397
[Merged] [flang][OpenMP] Add test for checking overloaded operator in atomic update
[Merged] [flang][OpenMP] Fix construct privatization in default clause. Relevant issue and issue
[Merged] [flang][Semantics] Threadprivate symbols are ignored in presence of default clause. Relevant issue
[Merged] Error out when assumed rank variable in used as selector in SELECT TYPE statement. Relevant issue
[Merged] Fix min reduction initialization
[Merged] Skip default privatization for crashing case
[Merged Fix common block missing symbol crash](llvm/llvm-project#67330) in response to issue Crash observed in threadprivatised common block
[Merged] Prevent extraneous copy in D156120
[Merged]Support for privatization in common block
[Merged]Support common block in OpenMP private clause
[Merged]Translating if and final clauses for task construct
[Merged]Fixed internal compiler error with atomic update operation verification
[Merged]Verify support for private/firstprivate on unstructured sections
[Merged]Handle lastprivate on sections construct
[Merged]Handle private/firstprivate clauses on sections construct
[Merged]Fix warning due to uninitialized pointer dereference during atomic update lowering
[Merged]Allow default(none) to access variables with PARAMETER attribute
[Merged]Semantic checks for symbols in atomic update assignment statement
Semantic checks for 'operator' in atomic update assignment statements
[Merged]Refactor code related to OpenMP atomic memory order clause semantics
Added semantic checks for atomic capture, write, and update statements
[Merged]Added semantic checks for hint clause
Lowering for atomic capture construct
Additional semantic checks for openmp atomic construct
[Merged]Lowering for atomic update construct
[Merged]Parser support for in_reduction clause on OpenMP task directive
[Merged]Lowering for default clause
[Merged]Cherrypicking atomic operations downstream
[Merged]Lowering for atomic read and write constructs
[Merged]Lowering for sections constructs
[Merged]Semantic checks for OpenMP constructs: sections and simd
[Merged]Semantic checks for OpenMP critical construct name resolution
[Merged]Semantic checks for OpenMP atomic construct
[Merged]Test case for semantic checks for OpenMP parallel sections contruct
A bunch of notes in addition to the discussion in the patches themselves
-
D108904 : contains a
std::visit(common::visitors{...})which can be used as a boilerplate to iterate overstd::variantanywhere in the codebase. -
D108904 : contains boilerplate
PreandPostfunctions to manageSemanticsContextanywhere in theSemanticsof the codebase -
D110714: contains a reusable
HasMemberway of recognising whether thetypenamein a templated function belongs to a certain variant. This could be treated as a complementary way tostd::visit. -
D127047: contains ideas on greating global variables (
embox,undefined, andhas_value). And a good example of usingstd::functionas passing arguments to a function.
- Printing an unassociated pointer or unallocated allocatable may cause one segfault in some cases if someone tries to test execution. So, add one external function call to handle unassociated pointer and unallocated allocatable variables.
-
Possibly the most important source code resides in
llvm-project/flang/include/flang/Parser/parse-tree.hwhich contains all the parse tree nodes' definition. -
The generic structure is the same for all. Suppose OpenMP critical construct needs to be defined. Therefore first the individual components will be defined:
struct OmpCriticalDirective,struct Block, andstruct OmpEndCriticalDirectiveand thenstruct OpenMPCriticalConstructshall be defined as a wrapper for a tuplestd::tuple<OmpCriticalDirective, Block, OmpEndCriticalDirective>. -
Most classes share code. Therefore you will find a lot of macros as class boilerplates. Boilerplates usually wrap very simple functionality and functions.
-
Most of the parsing activities are done by
TYPE_PARSEthat is defined inllvm-project/flang/lib/Parser/type-parser-implementation.h, which is a wrapper aroundparser.Parse(state)wherestateisParserState. The actualstruct Parsertemplate is defined inllvm-project/flang/lib/Parser/type-parsers.hwhich is the base for all other parser instantiations.
-
A
std::variantofConstructNodeis maintained as theConstructStackthat pushes and pops contextual information (especially when a certain parser node is encountered while navigating the parse tree). This is very useful to look for nesting behaviour or to see if something is properly enclosed within a required construct. Examples ofConstructNodeincludeDoConstruct,IfConstructetc. However, as of August 30, 2021 (while working on patch D108904),OpenMPConstructis not a part of the stack. It is maintained as a separateSemanticsContext -
parser::Walkcan be used as an iterator over the AST nodes. One argument taken byparser::Walkis a certain template class which encapsulates functionality on what to do when certain nodes are encountered: see forPreandPostexamples inNoBranchingEnforceinflang/lib/Semantics/check-directive-structure.h -
Things like
OpenMPLoopConstructwhich have a bunch of things like a begin directive, optionalDoConstructand an optional end directive are implemented asstd::tuple. All parser nodes can be found inllvm-project/flang/include/flang/Parser/parse-tree.h -
Most of the classes in
/flang/include/flang/Parser/parse-tree.hare created as boilerplate macros which makes a lot of sense: common code defined as macro expansion. Most classes simply wrap a structure:std::tupleorstd::variantor popularly theCharBlock(dealt with in/flang/include/Parser/char-block.h: which basically defines a single alphanumeric character mapped to a parser node to trace back source and other contextual information) -
Best way to check OpenMP semantics is to head over to the file
llvm-project/flang/lib/Semantics/check-omp-structure.cpp. Let's say you need to semantically validateOpenMPAtomicConstruct. Create a suitable class (outside ofOmpStructureChecker) and withinOmpStructureChecker::Enter(const auto OpenMPAtomicConstruct&), initiate aparser::walk(..., <class here>)with<class here>replaced with the object of the class you created. Then this<class here>can have all sorts ofPreandPostyou need for the semantic checks.
What is OpenMP?: OpenMP is an application programming interface that supports multi-platform shared-memory multiprocessing programming in C, C++, and Fortran, on many platforms, instruction-set architectures and operating systems, including Solaris, AIX, HP-UX, Linux, macOS, and Windows.
sectionsdirective: defines independent sections that can be distributed amongst threads. Example
program sample
use omp_lib
!$omp sections
!$omp section
! do work here to be given to worker thread 1
!$omp section
! do work here to be given to worker thread 2
!$omp end sections
end program sample-
simddirective: defines the things related to SIMD instructions (mainly have non-branching loops within them) -
atomicdirective: defines a set of statements which must be done atomically. Can include a bunch of reads, writes, updates etc. Defined asOpenMPAtomicConstructnode inllvm-project/flang/include/flang/Parser/parse-tree.h. -
critical: defines a section of code as critical, implies only one thread in a thread worker group shall enter into the section at a time
OpenMP is a bunch of pragmas you can put in your code to tell the compiler how to handle them exactly. For example, !$omp atomic read above a x = y means the compiler should perform this atomically. OpenMP is a dialect. And it is upto compiler engineers to build support in both flang and mlir for OpenMP dialect.
-
OpenMP standard (updated till standard 5.1) has
hintexpressions attached with OpenMP constructs. These are mostly synchronization hints that you use provide to the compiler in order to optimize critical sections (thereby two most popular constructs here areatomicandcritical). Few main types of hint expressions:-- uncontended: expect low contention. Means expect few threads will contend for this particular critical section
-- contended: expect high contention.
-- speculative: use speculative techniques like transactional memory. Just like DBMS involves transactions as a group (they are committed and rolled back as a group , similar ideas are ported to concurrent executions of critical sections
-- non-speculative: do not use speculative techniques as transactional memory
-
Getting expressions is one of the core things you have to perform a lot. The
GetExprinterface is defined inflang/include/flang/Semantics/tools.h. In MLIR, use AbstractConverter'sgenExprValue; you may have to work withfir::ExtendableValue(which is further defined inflang/Optimizer/Builder/BoxValue.h). From here, you can extractmlir::Valuethrough afir::getBase(this gets the final value [the temporary variable number] from the computation of the entire expression.
-
An infrastructure where you can define a bunch of things aiding your compiler infrastructure needs. You can define your own operations, type systems etc and reuse MLIR's pass management, threading etc to get a compiler infrastructure up and running really quick. For example, we import MLIR functionality into flang, lower PFT to MLIR, and let MLIR do its magic, and finally lower to LLVM. This integration is seemless.
-
MLIR can be used to design custom IR for a variety of use-cases. For example, the same MLIR infra can be used to generate code for Fortran as well as Tensorflow.
-
Clang builds some AST and performs transformations on the same. This is fairly successful. But people have started looking at how retaining some of the high level semantics can help in better transformations. LLVM IR loses this context; thus MLIR hopes to retain such high level semantic information and perform transformations on the same.
-
Dominance checking: A part of code resides in
mlir/lib/IR/Verifier.cppwhich manipulates the regions of different things, understands what region envelops what region, and so on.
- Fortran has the concept of arrays whose lower bound, upper bound, and sizes are defined at runtime. This means we have to keep this information somewhere in memory. And the best place to do that is the array itself. Fortran array descriptors abstract these informations (how many dimensions, what the stride from one element to the next. It also contains type information for the type itself). In FIR dialect, the
fir.boxis used to say this is a descriptor andfir.emboxis used to say package this as a descriptor.
MORE INFORMATION WILL BE ADDED AS TIME PASSES
-
You need to have a bunch of definable operations which you can create lowering code for. In namespace
mlirinllvm-project/mlir/lib/Conversion/PassDetails.h, there is a child namespace callednamespace ompthat encapsulatesclass OpenMPDialectwhich in turn is actually in an inc file you will find in yourbuildandinstallfolder.build:tools/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsDialect.h.inc; similarly forinstallfolder too. -
To include a new operation, do it in
llvm-project/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td. The corresponding created class will be ininstall/include/mlir/Dialect/OpenMP/OpenMPOps.h.incand all function definitions will be ininstall/include/mlir/Dialect/OpenMP/OpenMPOps.cpp.inc. You can define your custom builders etc in thellvm-project/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.tdfile, whose definition goes inllvm-project/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp. -
For
Variadic<AnyType>:$private_varsandVariadic<AnyType>:$firstprivate_vars, you need to extract the clause list, extract these clauses, and convert their arguments to an object list throughgenObjectListthat fits in everything inSmallVector<Value>.genObjectListbasically extracts the symbol of every argument and gets an address reference for the same. This is later used to create FIR. -
Most of the OMP clauses are defined in
llvm-project/llvm/include/llvm/Frontend/OpenMP/OMP.td. For example
def OMPC_Reduction : Clause<"reduction">{
let clangClass = "OMPReductionClause";
let flangClass = "OmpReductionClause";
}
def OMP_Sections : Directive<"sections">{
let allowedClauses = [
VersionedClause<OMPC_Private>,
VersionedClause<OMPC_LastPrivate>,
VersionedClause<OMPC_FirstPrivate>,
VersionedClause<OMPC_Reduction>,
VersionedClause<OMPC_NoWait>,
VersionedClause<OMPC_Allocate>
];
}
basically define OMP classes for both clang and flang, and a bunch of pragmas or directives to be used in both places as well as the allowed clauses in the clause list.
-
To know how exactly the clauses must be handled, refer to
llvm-project/llvm/include/llvm/Frontend/OpenMP/OMP.td, pick one clause likeOMPC_Reduction, and check itsclangClassandflangClassentry. ThisflangClass's details will be found inllvm-project/flang/include/flang/Parser/parse-tree.h. -
MLIR is a general purpose intermediate representation. It does not understand external types. In order to do so, you need to do a
public mlir::omp::PointerLikeType::ExternalModelwherein you attach an external type to the pointer model. This is just a wrapper around agetElementTypemethod that casts themlir::Type pointerto the type that MLIR wants to capture from the external source. -
In
llvm/include/llvm/Frontend/OpenMP/OMP.td, OMPC (OMP clauses) are defined as given here. In order to lower these, MLIR usually expects aString::Attr, wherein you need to convert the enum clause value tomlir::StringAttr. In order to do so, MLIR provides an interfacestringifyClause + <NAME>(likestringifyClauseMemoryOrderKind) which takes in an argument of the formomp::ClauseMemoryOrderKind::acquire. This will end up stringifying the entire thing.
def OMPC_MemoryOrder : Clause<"memory_order"> {
let enumClauseValue = "MemoryOrderKind";
let allowedClauseValues = [
OMP_MEMORY_ORDER_SeqCst,
OMP_MEMORY_ORDER_AcqRel,
OMP_MEMORY_ORDER_Acquire,
OMP_MEMORY_ORDER_Release,
OMP_MEMORY_ORDER_Relaxed,
OMP_MEMORY_ORDER_Default
];
}
Verbatim copy of the important patch discussions to keep everything in one place
The following patch implements the following semantic checks for atomic construct based on OpenMP 5.0 specifications.
- *In atomic update statement, binary operator is one of +, , -, /, .AND., .OR., .EQV., or .NEQV.
In llvm-project/flang/lib/Semantics/check-omp-structure.cpp, a new class OmpAtomicConstructChecker is added for all semantic checks implemented in this patch. This class is used in a parser::Walk in OmpStructureChecker::CheckOmpAtomicConstructStructure in check-omp-structure.cpp itself.
The entire aim is to, for any OpenMP atomic update statement, initiate a parser::Walk on the construct, capture the assignment statement in bool Pre(const parser::AssignmentStmt &assignment) inside OmpAtomicConstructChecker, and initiate a check with CheckOperatorValidity for the operator validity.
CheckOperatorValidity has two HasMember checks. The first check is to see whether the assignment statement has a binary operator or not. Because if it doesn't, the call to CompareNames is unsuccessful. The second check is to see if the operator is an allowed binary operator.
- In atomic update statement, the statements must be of the form x = x operator expr or x = expr operator x
The CompareNames function called from within CheckOperatorValidity checks that if we have an assignment statement with a binary operator, whether the variable names are same on the LHS and RHS.
- In atomic update statement, only the following intrinsic procedures are allowed: MAX, MIN, IAND, IOR, or IEOR
The functionality for the same is added in the function CheckProcedureDesignatorValidity.
Alternative approaches tried which we could discuss upon:
-
In
CheckOperatorValidity, I earlier tried astd::visitapproach. But that required us to enumerate all dis-allowed binary operators. Because had we emitted errors for anything that is not allowed, we began facing issues with assignment statements in atomic READ statements. -
In
llvm-project/flang/include/flang/Parser/parse-tree.h, thestruct Exprhas a variant where all relevant operators and procedure indirection things are present. One interesting structure isIntrinsicBinarywhich is the base structure for all relevant binary operators inparser::Expr. However, we found no way to capture the different binary operators through this base structure. Concretely, I was thinking something like this:
std::visit(common::visitors{
[&](const parser::Expr::IntrinsicBinary &x){
// check for validity of variable names as well as operator validity
},
[&](const auto &x){
// got something other than a valid binary operator. Let it pass.
},
}, node);Taken forward from https://reviews.llvm.org/D93051 (authored by @sameeranjoshi )
As reported in https://bugs.llvm.org/show_bug.cgi?id=48145, name resolution for omp critical construct was failing. This patch adds functionality to help that name resolution as well as implementation to catch name mismatches.
- Changes to check-omp-structure.cpp
In llvm-project/flang/lib/Semantics/check-omp-structure.cpp, under void OmpStructureChecker::Enter(const parser::OpenMPCriticalConstruct &x), logic is added to handle the different forms of name mismatches and report appropriate error. The following semantic restrictions are therefore handled here:
- If a name is specified on a critical directive, the same name must also be specified on the end critical directive
- If no name appears on the critical directive, no name can appear on the end critical directive
- If a name appears on either the start critical directive or the end critical directive
The only allowed combinations are: (1) both start and end directives have same name, (2) both start and end directives have NO name altogether
- Changes to resolve-directives.cpp
In llvm-project/flang/lib/Semantics/resolve-directives.cpp, two Pre functions are added for OmpCriticalDirective and OmpEndCriticalDirective that invoke the services of ResolveOmpName to give new symbols to the names. This aids in removing the undesirable behavior noted in https://bugs.llvm.org/show_bug.cgi?id=48145
According to OpenMP 5.0 spec document, the following semantic restrictions have been dealt with in this patch.
- [sections] Orphaned section directives are prohibited. That is, the section directives must appear within the sections construct and must not be encountered elsewhere in the sections region.
Semantic checks for the following are not necessary, since use of orphaned section construct (i.e. without an enclosing sections directive) throws parser errors and control flow never reaches the semantic checking phase.
Added a test case for the same in llvm-project/flang/test/Parser/omp-sections01.f90
- [sections] Must be a structured block
Added test case as llvm-project/flang/test/Semantics/omp-sections02.f90 and made changes to branching logic in llvm-project/flang/lib/Semantics/check-directive-structure.h as follows:
NoBranchingEnforce is used within a parser::Walk called from CheckNoBranching (in check-directive-structure.h itself). CheckNoBranching is invoked from a lot of places, but of our interest in this particular context is void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) (defined within llvm-project/flang/lib/Semantics/check-omp-structure.cpp)
[addition] NoBranchingEnforce lacked tracking context earlier (precisely once control flow entered a OpenMP directive, constructs were no longer being recorded on the ConstructStack). Relevant Pre and Post functions added for the same
[addition] A EmitBranchOutError on encountering a CALL in a structured block
[changes] Although NoBranchingEnforce was handling labelled EXITs and CYCLEs well, there were no semantic checks for unlabelled CYCLEs and EXITs. Support added for them as well. For unlabeled cycles and exits, there's a need to efficiently differentiate the ConstructNode added to the stack before the concerned OpenMP directive was encountered and the nodes added after the directive was encountered. The same is done through a simple private unsigned counter within NoBranchingEnforce itself
[addition] Addition of EmitUnlabelledBranchOutError is due to lack of an error message that gave proper context to the programmer. Existing error messages are either named error messages (which aren't valid for unlabeled cycles and exits) or a simple CYCLE/EXIT statement not allowed within SECTIONS construct which is more generic than the reality.
Other than these implementations, a test case for simd construct has been added.
- [Test case for simd] Must be a structured block / A program that branches in or out of a function with declare simd is non conforming.
Uses the already existing branching out of OpenMP structured block logic as well as changes introduced above to semantically validate the restriction. Test case added to llvm-project/flang/test/Semantics/omp-simd01.f90
High priority TODOs:
- (done) Decide whether CALL is an invalid branch out of an OpenMP structured block
- (done) Fix !$omp do's handling of unlabeled CYCLEs