Index: flang/include/flang/Lower/AbstractConverter.h =================================================================== --- flang/include/flang/Lower/AbstractConverter.h +++ flang/include/flang/Lower/AbstractConverter.h @@ -107,7 +107,7 @@ virtual void collectSymbolSet( pft::Evaluation &eval, llvm::SetVector &symbolSet, - Fortran::semantics::Symbol::Flag flag) = 0; + Fortran::semantics::Symbol::Flag flag, bool isOriginalSymbol = false) = 0; //===--------------------------------------------------------------------===// // Expressions Index: flang/include/flang/Lower/SymbolMap.h =================================================================== --- flang/include/flang/Lower/SymbolMap.h +++ flang/include/flang/Lower/SymbolMap.h @@ -302,6 +302,13 @@ return shallowLookupSymbol(*sym); } + /// Find `symbol` and return its value if it appears in the host level map + /// for the host variable in host-association. + SymbolBox lookupHostSymbol(semantics::SymbolRef sym); + SymbolBox lookupHostSymbol(const semantics::Symbol *sym) { + return lookupHostSymbol(*sym); + } + /// Add a new binding from the ac-do-variable `var` to `value`. void pushImpliedDoBinding(AcDoVar var, mlir::Value value) { impliedDoStack.emplace_back(var, value); Index: flang/lib/Lower/Bridge.cpp =================================================================== --- flang/lib/Lower/Bridge.cpp +++ flang/lib/Lower/Bridge.cpp @@ -481,16 +481,18 @@ assert(sym.has() && "No host-association found"); const Fortran::semantics::Symbol &hsym = sym.GetUltimate(); - Fortran::lower::SymbolBox hsb = lookupSymbol(hsym); + Fortran::lower::SymbolBox hsb = lookupHostSymbol(hsym); + assert(hsb && "Host symbol box not found"); fir::ExtendedValue hexv = getExtendedValue(hsb); - // 2) Create a copy that will mask the original. - createHostAssociateVarClone(sym); + // 2) Fetch the copied one that will mask the original. Fortran::lower::SymbolBox sb = lookupSymbol(sym); + assert(sb && "Host-associated symbol box not found"); fir::ExtendedValue exv = getExtendedValue(sb); // 3) Perform the assignment. - mlir::Location loc = genLocation(sym.name()); + builder->setInsertionPointAfter(fir::getBase(exv).getDefiningOp()); + mlir::Location loc = getCurrentLocation(); mlir::Type symType = genType(sym); if (auto seqTy = symType.dyn_cast()) { Fortran::lower::StatementContext stmtCtx; @@ -514,11 +516,13 @@ void collectSymbolSet( Fortran::lower::pft::Evaluation &eval, llvm::SetVector &symbolSet, - Fortran::semantics::Symbol::Flag flag) override final { + Fortran::semantics::Symbol::Flag flag, + bool isOriginalSymbol) override final { auto addToList = [&](const Fortran::semantics::Symbol &sym) { - const Fortran::semantics::Symbol &ultimate = sym.GetUltimate(); - if (ultimate.test(flag)) - symbolSet.insert(&ultimate); + const Fortran::semantics::Symbol &symbol = + isOriginalSymbol ? sym : sym.GetUltimate(); + if (symbol.test(flag)) + symbolSet.insert(&symbol); }; Fortran::lower::pft::visitAllSymbols(eval, addToList); } @@ -609,6 +613,15 @@ return {}; } + /// Find the symbol in the host level of symbol map for host-association + /// or return null. + Fortran::lower::SymbolBox + lookupHostSymbol(const Fortran::semantics::Symbol &sym) { + if (Fortran::lower::SymbolBox v = localSymbols.lookupHostSymbol(sym)) + return v; + return {}; + } + /// Add the symbol to the local map and return `true`. If the symbol is /// already in the map and \p forced is `false`, the map is not updated. /// Instead the value `false` is returned. Index: flang/lib/Lower/OpenMP.cpp =================================================================== --- flang/lib/Lower/OpenMP.cpp +++ flang/lib/Lower/OpenMP.cpp @@ -69,12 +69,11 @@ // variables) happen separately, for everything else privatize here. if (sym->test(Fortran::semantics::Symbol::Flag::OmpPreDetermined)) continue; + bool success = converter.createHostAssociateVarClone(*sym); + (void)success; + assert(success && "Privatization failed due to existing binding"); if constexpr (std::is_same_v) { converter.copyHostAssociateVar(*sym); - } else { - bool success = converter.createHostAssociateVarClone(*sym); - (void)success; - assert(success && "Privatization failed due to existing binding"); } } } @@ -157,9 +156,10 @@ }; llvm::SetVector threadprivateSyms; - converter.collectSymbolSet( - eval, threadprivateSyms, - Fortran::semantics::Symbol::Flag::OmpThreadprivate); + converter.collectSymbolSet(eval, threadprivateSyms, + Fortran::semantics::Symbol::Flag::OmpThreadprivate, + /*isOriginalSymbol=*/true); + std::set threadprivateSymNames; // For a COMMON block, the ThreadprivateOp is generated for itself instead of // its members, so only bind the value of the new copied ThreadprivateOp @@ -169,6 +169,12 @@ for (std::size_t i = 0; i < threadprivateSyms.size(); i++) { auto sym = threadprivateSyms[i]; mlir::Value symThreadprivateValue; + // The variable may be used more than once, and each reference has one + // symbol with the same name. Only do once for references of one variable. + if (threadprivateSymNames.find(sym->name()) != threadprivateSymNames.end()) + continue; + else + threadprivateSymNames.insert(sym->name()); if (const Fortran::semantics::Symbol *common = Fortran::semantics::FindCommonBlockContaining(sym->GetUltimate())) { mlir::Value commonThreadprivateValue; @@ -194,6 +200,36 @@ firOpBuilder.restoreInsertionPoint(insPt); } +static void +genCopyinClause(Fortran::lower::AbstractConverter &converter, + const Fortran::parser::OmpClauseList &opClauseList) { + fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); + auto insPt = firOpBuilder.saveInsertionPoint(); + firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); + bool hasCopyin{false}; + for (const Fortran::parser::OmpClause &clause : opClauseList.v) { + if (const auto ©inClause = + std::get_if(&clause.u)) { + hasCopyin = true; + const Fortran::parser::OmpObjectList &ompObjectList = copyinClause->v; + for (const Fortran::parser::OmpObject &ompObject : ompObjectList.v) { + Fortran::semantics::Symbol *sym = getOmpObjectSymbol(ompObject); + if (sym->has()) + TODO(converter.getCurrentLocation(), "common block in Copyin clause"); + if (Fortran::semantics::IsAllocatableOrPointer(sym->GetUltimate())) + TODO(converter.getCurrentLocation(), + "pointer or allocatable variables in Copyin clause"); + assert(sym->has() && + "No host-association found"); + converter.copyHostAssociateVar(*sym); + } + } + } + if (hasCopyin) + firOpBuilder.create(converter.getCurrentLocation()); + firOpBuilder.restoreInsertionPoint(insPt); +} + static void genObjectList(const Fortran::parser::OmpObjectList &objectList, Fortran::lower::AbstractConverter &converter, llvm::SmallVectorImpl &operands) { @@ -339,8 +375,11 @@ if (clauses && !outerCombined) privatizeVars(converter, *clauses); - if (std::is_same_v) + if (std::is_same_v) { threadPrivatizeVars(converter, eval); + if (clauses) + genCopyinClause(converter, *clauses); + } } static void genOMP(Fortran::lower::AbstractConverter &converter, @@ -486,7 +525,6 @@ std::get(directive.t); // TODO: Handle the following clauses // 1. default - // 2. copyin // Note: rest of the clauses are handled when the inner operation is created for (const Fortran::parser::OmpClause &clause : opClauseList.v) { if (const auto &ifClause = @@ -566,8 +604,9 @@ allocateOperands); } else if (std::get_if(&clause.u) || std::get_if( - &clause.u)) { - // Privatisation clauses are handled elsewhere. + &clause.u) || + std::get_if(&clause.u)) { + // Privatisation and copyin clauses are handled elsewhere. continue; } else if (std::get_if(&clause.u)) { // Nothing needs to be done for threads clause. Index: flang/lib/Lower/SymbolMap.cpp =================================================================== --- flang/lib/Lower/SymbolMap.cpp +++ flang/lib/Lower/SymbolMap.cpp @@ -51,6 +51,22 @@ return SymbolBox::None{}; } +Fortran::lower::SymbolBox +Fortran::lower::SymMap::lookupHostSymbol(Fortran::semantics::SymbolRef symRef) { + Fortran::semantics::SymbolRef sym = symRef.get().GetUltimate(); + auto jmap = symbolMapStack.rbegin(); + auto jend = symbolMapStack.rend(); + if (jmap == jend) + return SymbolBox::None{}; + // Skip the associated SymMap stack level. + for (++jmap; jmap != jend; ++jmap) { + auto iter = jmap->find(&*sym); + if (iter != jmap->end()) + return iter->second; + } + return SymbolBox::None{}; +} + mlir::Value Fortran::lower::SymMap::lookupImpliedDo(Fortran::lower::SymMap::AcDoVar var) { for (auto [marker, binding] : llvm::reverse(impliedDoStack)) Index: flang/lib/Semantics/resolve-directives.cpp =================================================================== --- flang/lib/Semantics/resolve-directives.cpp +++ flang/lib/Semantics/resolve-directives.cpp @@ -476,7 +476,8 @@ static constexpr Symbol::Flags ompFlagsRequireNewSymbol{ Symbol::Flag::OmpPrivate, Symbol::Flag::OmpLinear, Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate, - Symbol::Flag::OmpReduction, Symbol::Flag::OmpCriticalLock}; + Symbol::Flag::OmpReduction, Symbol::Flag::OmpCriticalLock, + Symbol::Flag::OmpCopyIn}; static constexpr Symbol::Flags ompFlagsRequireMark{ Symbol::Flag::OmpThreadprivate}; @@ -580,6 +581,10 @@ if (object.owner() != currScope()) { auto &symbol{MakeAssocSymbol(object.name(), object, scope)}; symbol.set(flag); + if (flag == Symbol::Flag::OmpCopyIn) { + // The symbol in copyin clause must be threadprivate entity. + symbol.set(Symbol::Flag::OmpThreadprivate); + } return &symbol; } else { object.set(flag); Index: flang/test/Lower/OpenMP/copyin.f90 =================================================================== --- /dev/null +++ flang/test/Lower/OpenMP/copyin.f90 @@ -0,0 +1,206 @@ +! This test checks lowering of `COPYIN` clause. +! RUN: bbc -fopenmp -emit-fir %s -o - | FileCheck %s +! RUN: %flang_fc1 -emit-fir -fopenmp %s -o - | FileCheck %s + +! CHECK-LABEL: func.func @_QPcopyin_scalar_array() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QFcopyin_scalar_arrayEx1) : !fir.ref +! CHECK: %[[VAL_1:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref -> !fir.ref +! CHECK: %[[VAL_2:.*]] = fir.address_of(@_QFcopyin_scalar_arrayEx2) : !fir.ref> +! CHECK: %[[VAL_3:.*]] = arith.constant 10 : index +! CHECK: %[[VAL_4:.*]] = omp.threadprivate %[[VAL_2]] : !fir.ref> -> !fir.ref> +! CHECK: omp.parallel { +! CHECK: %[[VAL_5:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref -> !fir.ref +! CHECK: %[[VAL_6:.*]] = fir.load %[[VAL_1]] : !fir.ref +! CHECK: fir.store %[[VAL_6]] to %[[VAL_5]] : !fir.ref +! CHECK: %[[VAL_7:.*]] = omp.threadprivate %[[VAL_2]] : !fir.ref> -> !fir.ref> +! CHECK: %[[VAL_8:.*]] = fir.shape %[[VAL_3]] : (index) -> !fir.shape<1> +! CHECK: %[[VAL_9:.*]] = fir.array_load %[[VAL_7]](%[[VAL_8]]) : (!fir.ref>, !fir.shape<1>) -> !fir.array<10xi64> +! CHECK: %[[VAL_10:.*]] = fir.shape %[[VAL_3]] : (index) -> !fir.shape<1> +! CHECK: %[[VAL_11:.*]] = fir.array_load %[[VAL_4]](%[[VAL_10]]) : (!fir.ref>, !fir.shape<1>) -> !fir.array<10xi64> +! CHECK: %[[VAL_12:.*]] = arith.constant 1 : index +! CHECK: %[[VAL_13:.*]] = arith.constant 0 : index +! CHECK: %[[VAL_14:.*]] = arith.subi %[[VAL_3]], %[[VAL_12]] : index +! CHECK: %[[VAL_15:.*]] = fir.do_loop %[[VAL_16:.*]] = %[[VAL_13]] to %[[VAL_14]] step %[[VAL_12]] unordered iter_args(%[[VAL_17:.*]] = %[[VAL_9]]) -> (!fir.array<10xi64>) { +! CHECK: %[[VAL_18:.*]] = fir.array_fetch %[[VAL_11]], %[[VAL_16]] : (!fir.array<10xi64>, index) -> i64 +! CHECK: %[[VAL_19:.*]] = fir.array_update %[[VAL_17]], %[[VAL_18]], %[[VAL_16]] : (!fir.array<10xi64>, i64, index) -> !fir.array<10xi64> +! CHECK: fir.result %[[VAL_19]] : !fir.array<10xi64> +! CHECK: } +! CHECK: fir.array_merge_store %[[VAL_9]], %[[VAL_20:.*]] to %[[VAL_7]] : !fir.array<10xi64>, !fir.array<10xi64>, !fir.ref> +! CHECK: omp.barrier +! CHECK: fir.call @_QPsub1(%[[VAL_5]], %[[VAL_7]]) : (!fir.ref, !fir.ref>) -> () +! CHECK: omp.terminator +! CHECK: } +! CHECK: return +! CHECK: } + +subroutine copyin_scalar_array() + integer(kind=4), save :: x1 + integer(kind=8), save :: x2(10) + !$omp threadprivate(x1, x2) + + !$omp parallel copyin(x1) copyin(x2) + call sub1(x1, x2) + !$omp end parallel + +end + +! CHECK-LABEL: func.func @_QPcopyin_char_chararray() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QFcopyin_char_chararrayEx3) : !fir.ref> +! CHECK: %[[VAL_1:.*]] = arith.constant 5 : index +! CHECK: %[[VAL_2:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref> -> !fir.ref> +! CHECK: %[[VAL_3:.*]] = fir.address_of(@_QFcopyin_char_chararrayEx4) : !fir.ref>> +! CHECK: %[[VAL_4:.*]] = arith.constant 5 : index +! CHECK: %[[VAL_5:.*]] = arith.constant 10 : index +! CHECK: %[[VAL_6:.*]] = omp.threadprivate %[[VAL_3]] : !fir.ref>> -> !fir.ref>> +! CHECK: omp.parallel { +! CHECK: %[[VAL_7:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref> -> !fir.ref> +! CHECK: %[[VAL_8:.*]] = arith.constant 1 : i64 +! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_1]] : (index) -> i64 +! CHECK: %[[VAL_10:.*]] = arith.muli %[[VAL_8]], %[[VAL_9]] : i64 +! CHECK: %[[VAL_11:.*]] = arith.constant false +! CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_7]] : (!fir.ref>) -> !fir.ref +! CHECK: %[[VAL_13:.*]] = fir.convert %[[VAL_2]] : (!fir.ref>) -> !fir.ref +! CHECK: fir.call @llvm.memmove.p0.p0.i64(%[[VAL_12]], %[[VAL_13]], %[[VAL_10]], %[[VAL_11]]) : (!fir.ref, !fir.ref, i64, i1) -> () +! CHECK: %[[VAL_14:.*]] = omp.threadprivate %[[VAL_3]] : !fir.ref>> -> !fir.ref>> +! CHECK: %[[VAL_15:.*]] = fir.shape %[[VAL_5]] : (index) -> !fir.shape<1> +! CHECK: %[[VAL_16:.*]] = fir.array_load %[[VAL_14]](%[[VAL_15]]) : (!fir.ref>>, !fir.shape<1>) -> !fir.array<10x!fir.char<1,5>> +! CHECK: %[[VAL_17:.*]] = fir.shape %[[VAL_5]] : (index) -> !fir.shape<1> +! CHECK: %[[VAL_18:.*]] = fir.array_load %[[VAL_6]](%[[VAL_17]]) : (!fir.ref>>, !fir.shape<1>) -> !fir.array<10x!fir.char<1,5>> +! CHECK: %[[VAL_19:.*]] = arith.constant 1 : index +! CHECK: %[[VAL_20:.*]] = arith.constant 0 : index +! CHECK: %[[VAL_21:.*]] = arith.subi %[[VAL_5]], %[[VAL_19]] : index +! CHECK: %[[VAL_22:.*]] = fir.do_loop %[[VAL_23:.*]] = %[[VAL_20]] to %[[VAL_21]] step %[[VAL_19]] unordered iter_args(%[[VAL_24:.*]] = %[[VAL_16]]) -> (!fir.array<10x!fir.char<1,5>>) { +! CHECK: %[[VAL_25:.*]] = fir.array_access %[[VAL_18]], %[[VAL_23]] : (!fir.array<10x!fir.char<1,5>>, index) -> !fir.ref> +! CHECK: %[[VAL_26:.*]] = fir.array_access %[[VAL_24]], %[[VAL_23]] : (!fir.array<10x!fir.char<1,5>>, index) -> !fir.ref> +! CHECK: %[[VAL_27:.*]] = arith.constant 5 : index +! CHECK: %[[VAL_28:.*]] = arith.constant 1 : i64 +! CHECK: %[[VAL_29:.*]] = fir.convert %[[VAL_27]] : (index) -> i64 +! CHECK: %[[VAL_30:.*]] = arith.muli %[[VAL_28]], %[[VAL_29]] : i64 +! CHECK: %[[VAL_31:.*]] = arith.constant false +! CHECK: %[[VAL_32:.*]] = fir.convert %[[VAL_26]] : (!fir.ref>) -> !fir.ref +! CHECK: %[[VAL_33:.*]] = fir.convert %[[VAL_25]] : (!fir.ref>) -> !fir.ref +! CHECK: fir.call @llvm.memmove.p0.p0.i64(%[[VAL_32]], %[[VAL_33]], %[[VAL_30]], %[[VAL_31]]) : (!fir.ref, !fir.ref, i64, i1) -> () +! CHECK: %[[VAL_34:.*]] = fir.array_amend %[[VAL_24]], %[[VAL_26]] : (!fir.array<10x!fir.char<1,5>>, !fir.ref>) -> !fir.array<10x!fir.char<1,5>> +! CHECK: fir.result %[[VAL_34]] : !fir.array<10x!fir.char<1,5>> +! CHECK: } +! CHECK: fir.array_merge_store %[[VAL_16]], %[[VAL_35:.*]] to %[[VAL_14]] : !fir.array<10x!fir.char<1,5>>, !fir.array<10x!fir.char<1,5>>, !fir.ref>> +! CHECK: omp.barrier +! CHECK: %[[VAL_36:.*]] = fir.convert %[[VAL_7]] : (!fir.ref>) -> !fir.ref> +! CHECK: %[[VAL_37:.*]] = fir.emboxchar %[[VAL_36]], %[[VAL_1]] : (!fir.ref>, index) -> !fir.boxchar<1> +! CHECK: %[[VAL_38:.*]] = fir.convert %[[VAL_14]] : (!fir.ref>>) -> !fir.ref> +! CHECK: %[[VAL_39:.*]] = fir.emboxchar %[[VAL_38]], %[[VAL_4]] : (!fir.ref>, index) -> !fir.boxchar<1> +! CHECK: fir.call @_QPsub2(%[[VAL_37]], %[[VAL_39]]) : (!fir.boxchar<1>, !fir.boxchar<1>) -> () +! CHECK: omp.terminator +! CHECK: } +! CHECK: return +! CHECK: } + +subroutine copyin_char_chararray() + character(5), save :: x3, x4(10) + !$omp threadprivate(x3, x4) + + !$omp parallel copyin(x3) copyin(x4) + call sub2(x3, x4) + !$omp end parallel + +end + +! CHECK-LABEL: func.func @_QPcopyin_derived_type() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QFcopyin_derived_typeEx5) : !fir.ref}>> +! CHECK: %[[VAL_1:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref}>> -> !fir.ref}>> +! CHECK: omp.parallel { +! CHECK: %[[VAL_2:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref}>> -> !fir.ref}>> +! CHECK: %[[VAL_3:.*]] = fir.load %[[VAL_1]] : !fir.ref}>> +! CHECK: fir.store %[[VAL_3]] to %[[VAL_2]] : !fir.ref}>> +! CHECK: omp.barrier +! CHECK: fir.call @_QPsub3(%[[VAL_2]]) : (!fir.ref}>>) -> () +! CHECK: omp.terminator +! CHECK: } +! CHECK: return +! CHECK: } + +subroutine copyin_derived_type() + type my_type + integer :: t_i + integer :: t_arr(5) + end type my_type + type(my_type), save :: x5 + !$omp threadprivate(x5) + + !$omp parallel copyin(x5) + call sub3(x5) + !$omp end parallel + +end + +! CHECK-LABEL: func.func @_QPcombined_parallel_worksharing_loop() { +! CHECK: %[[VAL_0:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFcombined_parallel_worksharing_loopEi"} +! CHECK: %[[VAL_1:.*]] = fir.address_of(@_QFcombined_parallel_worksharing_loopEx6) : !fir.ref +! CHECK: %[[VAL_2:.*]] = omp.threadprivate %[[VAL_1]] : !fir.ref -> !fir.ref +! CHECK: omp.parallel { +! CHECK: %[[VAL_3:.*]] = fir.alloca i32 {adapt.valuebyref, pinned} +! CHECK: %[[VAL_4:.*]] = omp.threadprivate %[[VAL_1]] : !fir.ref -> !fir.ref +! CHECK: %[[VAL_5:.*]] = fir.load %[[VAL_2]] : !fir.ref +! CHECK: fir.store %[[VAL_5]] to %[[VAL_4]] : !fir.ref +! CHECK: omp.barrier +! CHECK: %[[VAL_6:.*]] = arith.constant 1 : i32 +! CHECK: %[[VAL_7:.*]] = fir.load %[[VAL_4]] : !fir.ref +! CHECK: %[[VAL_8:.*]] = arith.constant 1 : i32 +! CHECK: omp.wsloop for (%[[VAL_9:.*]]) : i32 = (%[[VAL_6]]) to (%[[VAL_7]]) inclusive step (%[[VAL_8]]) { +! CHECK: fir.store %[[VAL_9]] to %[[VAL_3]] : !fir.ref +! CHECK: fir.call @_QPsub4(%[[VAL_4]]) : (!fir.ref) -> () +! CHECK: omp.yield +! CHECK: } +! CHECK: omp.terminator +! CHECK: } +! CHECK: return +! CHECK: } + +subroutine combined_parallel_worksharing_loop() + integer, save :: x6 + !$omp threadprivate(x6) + + !$omp parallel do copyin(x6) + do i=1, x6 + call sub4(x6) + end do + !$omp end parallel do + +end + +! CHECK-LABEL: func.func @_QPcombined_parallel_sections() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QFcombined_parallel_sectionsEx7) : !fir.ref +! CHECK: %[[VAL_1:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref -> !fir.ref +! CHECK: omp.parallel { +! CHECK: %[[VAL_2:.*]] = omp.threadprivate %[[VAL_0]] : !fir.ref -> !fir.ref +! CHECK: %[[VAL_3:.*]] = fir.load %[[VAL_1]] : !fir.ref +! CHECK: fir.store %[[VAL_3]] to %[[VAL_2]] : !fir.ref +! CHECK: omp.barrier +! CHECK: omp.sections { +! CHECK: omp.section { +! CHECK: fir.call @_QPsub5(%[[VAL_2]]) : (!fir.ref) -> () +! CHECK: omp.terminator +! CHECK: } +! CHECK: omp.section { +! CHECK: fir.call @_QPsub6(%[[VAL_2]]) : (!fir.ref) -> () +! CHECK: omp.terminator +! CHECK: } +! CHECK: omp.terminator +! CHECK: } +! CHECK: omp.terminator +! CHECK: } +! CHECK: return +! CHECK: } + +subroutine combined_parallel_sections() + integer, save :: x7 + !$omp threadprivate(x7) + + !$omp parallel sections copyin(x7) + !$omp section + call sub5(x7) + !$omp section + call sub6(x7) + !$omp end parallel sections + +end