FLANG
DirectivesCommon.h
1//===-- Lower/DirectivesCommon.h --------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
10//
11//===----------------------------------------------------------------------===//
16//===----------------------------------------------------------------------===//
17
18#ifndef FORTRAN_LOWER_DIRECTIVES_COMMON_H
19#define FORTRAN_LOWER_DIRECTIVES_COMMON_H
20
21#include "flang/Common/idioms.h"
22#include "flang/Evaluate/tools.h"
23#include "flang/Lower/AbstractConverter.h"
24#include "flang/Lower/Bridge.h"
25#include "flang/Lower/ConvertExpr.h"
26#include "flang/Lower/ConvertVariable.h"
27#include "flang/Lower/OpenACC.h"
28#include "flang/Lower/OpenMP.h"
29#include "flang/Lower/PFTBuilder.h"
30#include "flang/Lower/StatementContext.h"
31#include "flang/Lower/Support/Utils.h"
32#include "flang/Optimizer/Builder/DirectivesCommon.h"
33#include "flang/Optimizer/Builder/HLFIRTools.h"
34#include "flang/Optimizer/Dialect/FIRType.h"
35#include "flang/Parser/parse-tree.h"
36#include "flang/Semantics/openmp-directive-sets.h"
37#include "flang/Semantics/tools.h"
38#include "mlir/Dialect/OpenACC/OpenACC.h"
39#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
40#include "mlir/Dialect/SCF/IR/SCF.h"
41#include "mlir/IR/Value.h"
42#include "llvm/Frontend/OpenMP/OMPConstants.h"
43#include <list>
44#include <type_traits>
45
46namespace Fortran {
47namespace lower {
48
51static inline void genOmpAtomicHintAndMemoryOrderClauses(
54 mlir::IntegerAttr &hint,
55 mlir::omp::ClauseMemoryOrderKindAttr &memoryOrder) {
56 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
57 for (const Fortran::parser::OmpAtomicClause &clause : clauseList.v) {
58 if (const auto *ompClause =
59 std::get_if<Fortran::parser::OmpClause>(&clause.u)) {
60 if (const auto *hintClause =
61 std::get_if<Fortran::parser::OmpClause::Hint>(&ompClause->u)) {
62 const auto *expr = Fortran::semantics::GetExpr(hintClause->v);
63 uint64_t hintExprValue = *Fortran::evaluate::ToInt64(*expr);
64 hint = firOpBuilder.getI64IntegerAttr(hintExprValue);
65 }
66 } else if (const auto *ompMemoryOrderClause =
67 std::get_if<Fortran::parser::OmpMemoryOrderClause>(
68 &clause.u)) {
69 if (std::get_if<Fortran::parser::OmpClause::Acquire>(
70 &ompMemoryOrderClause->v.u)) {
71 memoryOrder = mlir::omp::ClauseMemoryOrderKindAttr::get(
72 firOpBuilder.getContext(),
73 mlir::omp::ClauseMemoryOrderKind::Acquire);
74 } else if (std::get_if<Fortran::parser::OmpClause::Relaxed>(
75 &ompMemoryOrderClause->v.u)) {
76 memoryOrder = mlir::omp::ClauseMemoryOrderKindAttr::get(
77 firOpBuilder.getContext(),
78 mlir::omp::ClauseMemoryOrderKind::Relaxed);
79 } else if (std::get_if<Fortran::parser::OmpClause::SeqCst>(
80 &ompMemoryOrderClause->v.u)) {
81 memoryOrder = mlir::omp::ClauseMemoryOrderKindAttr::get(
82 firOpBuilder.getContext(),
83 mlir::omp::ClauseMemoryOrderKind::Seq_cst);
84 } else if (std::get_if<Fortran::parser::OmpClause::Release>(
85 &ompMemoryOrderClause->v.u)) {
86 memoryOrder = mlir::omp::ClauseMemoryOrderKindAttr::get(
87 firOpBuilder.getContext(),
88 mlir::omp::ClauseMemoryOrderKind::Release);
89 }
90 }
91 }
92}
93
94template <typename AtomicListT>
95static void processOmpAtomicTODO(mlir::Type elementType,
96 [[maybe_unused]] mlir::Location loc) {
97 if (!elementType)
98 return;
99 if constexpr (std::is_same<AtomicListT,
101 assert(fir::isa_trivial(fir::unwrapRefType(elementType)) &&
102 "is supported type for omp atomic");
103 }
104}
105
108template <typename AtomicListT>
109static inline void genOmpAccAtomicCaptureStatement(
110 Fortran::lower::AbstractConverter &converter, mlir::Value fromAddress,
111 mlir::Value toAddress,
112 [[maybe_unused]] const AtomicListT *leftHandClauseList,
113 [[maybe_unused]] const AtomicListT *rightHandClauseList,
114 mlir::Type elementType, mlir::Location loc) {
115 // Generate `atomic.read` operation for atomic assigment statements
116 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
117
118 processOmpAtomicTODO<AtomicListT>(elementType, loc);
119
120 if constexpr (std::is_same<AtomicListT,
122 // If no hint clause is specified, the effect is as if
123 // hint(omp_sync_hint_none) had been specified.
124 mlir::IntegerAttr hint = nullptr;
125
126 mlir::omp::ClauseMemoryOrderKindAttr memoryOrder = nullptr;
127 if (leftHandClauseList)
128 genOmpAtomicHintAndMemoryOrderClauses(converter, *leftHandClauseList,
129 hint, memoryOrder);
130 if (rightHandClauseList)
131 genOmpAtomicHintAndMemoryOrderClauses(converter, *rightHandClauseList,
132 hint, memoryOrder);
133 firOpBuilder.create<mlir::omp::AtomicReadOp>(
134 loc, fromAddress, toAddress, mlir::TypeAttr::get(elementType), hint,
135 memoryOrder);
136 } else {
137 firOpBuilder.create<mlir::acc::AtomicReadOp>(
138 loc, fromAddress, toAddress, mlir::TypeAttr::get(elementType));
139 }
140}
141
144template <typename AtomicListT>
145static inline void genOmpAccAtomicWriteStatement(
146 Fortran::lower::AbstractConverter &converter, mlir::Value lhsAddr,
147 mlir::Value rhsExpr, [[maybe_unused]] const AtomicListT *leftHandClauseList,
148 [[maybe_unused]] const AtomicListT *rightHandClauseList, mlir::Location loc,
149 mlir::Value *evaluatedExprValue = nullptr) {
150 // Generate `atomic.write` operation for atomic assignment statements
151 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
152
153 mlir::Type varType = fir::unwrapRefType(lhsAddr.getType());
154 // Create a conversion outside the capture block.
155 auto insertionPoint = firOpBuilder.saveInsertionPoint();
156 firOpBuilder.setInsertionPointAfter(rhsExpr.getDefiningOp());
157 rhsExpr = firOpBuilder.createConvert(loc, varType, rhsExpr);
158 firOpBuilder.restoreInsertionPoint(insertionPoint);
159
160 processOmpAtomicTODO<AtomicListT>(varType, loc);
161
162 if constexpr (std::is_same<AtomicListT,
164 // If no hint clause is specified, the effect is as if
165 // hint(omp_sync_hint_none) had been specified.
166 mlir::IntegerAttr hint = nullptr;
167 mlir::omp::ClauseMemoryOrderKindAttr memoryOrder = nullptr;
168 if (leftHandClauseList)
169 genOmpAtomicHintAndMemoryOrderClauses(converter, *leftHandClauseList,
170 hint, memoryOrder);
171 if (rightHandClauseList)
172 genOmpAtomicHintAndMemoryOrderClauses(converter, *rightHandClauseList,
173 hint, memoryOrder);
174 firOpBuilder.create<mlir::omp::AtomicWriteOp>(loc, lhsAddr, rhsExpr, hint,
175 memoryOrder);
176 } else {
177 firOpBuilder.create<mlir::acc::AtomicWriteOp>(loc, lhsAddr, rhsExpr);
178 }
179}
180
183template <typename AtomicListT>
184static inline void genOmpAccAtomicUpdateStatement(
185 Fortran::lower::AbstractConverter &converter, mlir::Value lhsAddr,
186 mlir::Type varType, const Fortran::parser::Variable &assignmentStmtVariable,
187 const Fortran::parser::Expr &assignmentStmtExpr,
188 [[maybe_unused]] const AtomicListT *leftHandClauseList,
189 [[maybe_unused]] const AtomicListT *rightHandClauseList, mlir::Location loc,
190 mlir::Operation *atomicCaptureOp = nullptr) {
191 // Generate `atomic.update` operation for atomic assignment statements
192 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
193 mlir::Location currentLocation = converter.getCurrentLocation();
194
195 // Create the omp.atomic.update or acc.atomic.update operation
196 //
197 // func.func @_QPsb() {
198 // %0 = fir.alloca i32 {bindc_name = "a", uniq_name = "_QFsbEa"}
199 // %1 = fir.alloca i32 {bindc_name = "b", uniq_name = "_QFsbEb"}
200 // %2 = fir.load %1 : !fir.ref<i32>
201 // omp.atomic.update %0 : !fir.ref<i32> {
202 // ^bb0(%arg0: i32):
203 // %3 = arith.addi %arg0, %2 : i32
204 // omp.yield(%3 : i32)
205 // }
206 // return
207 // }
208
209 auto getArgExpression =
210 [](std::list<parser::ActualArgSpec>::const_iterator it) {
211 const auto &arg{std::get<parser::ActualArg>((*it).t)};
212 const auto *parserExpr{
213 std::get_if<common::Indirection<parser::Expr>>(&arg.u)};
214 return parserExpr;
215 };
216
217 // Lower any non atomic sub-expression before the atomic operation, and
218 // map its lowered value to the semantic representation.
219 Fortran::lower::ExprToValueMap exprValueOverrides;
220 // Max and min intrinsics can have a list of Args. Hence we need a list
221 // of nonAtomicSubExprs to hoist. Currently, only the load is hoisted.
223 Fortran::common::visit(
225 [&](const common::Indirection<parser::FunctionReference> &funcRef)
226 -> void {
227 const auto &args{std::get<std::list<parser::ActualArgSpec>>(
228 funcRef.value().v.t)};
229 std::list<parser::ActualArgSpec>::const_iterator beginIt =
230 args.begin();
231 std::list<parser::ActualArgSpec>::const_iterator endIt = args.end();
232 const auto *exprFirst{getArgExpression(beginIt)};
233 if (exprFirst && exprFirst->value().source ==
234 assignmentStmtVariable.GetSource()) {
235 // Add everything except the first
236 beginIt++;
237 } else {
238 // Add everything except the last
239 endIt--;
240 }
241 std::list<parser::ActualArgSpec>::const_iterator it;
242 for (it = beginIt; it != endIt; it++) {
243 const common::Indirection<parser::Expr> *expr =
244 getArgExpression(it);
245 if (expr)
246 nonAtomicSubExprs.push_back(Fortran::semantics::GetExpr(*expr));
247 }
248 },
249 [&](const auto &op) -> void {
250 using T = std::decay_t<decltype(op)>;
251 if constexpr (std::is_base_of<
253 T>::value) {
254 const auto &exprLeft{std::get<0>(op.t)};
255 const auto &exprRight{std::get<1>(op.t)};
256 if (exprLeft.value().source == assignmentStmtVariable.GetSource())
257 nonAtomicSubExprs.push_back(
258 Fortran::semantics::GetExpr(exprRight));
259 else
260 nonAtomicSubExprs.push_back(
261 Fortran::semantics::GetExpr(exprLeft));
262 }
263 },
264 },
265 assignmentStmtExpr.u);
266 StatementContext nonAtomicStmtCtx;
267 if (!nonAtomicSubExprs.empty()) {
268 // Generate non atomic part before all the atomic operations.
269 auto insertionPoint = firOpBuilder.saveInsertionPoint();
270 if (atomicCaptureOp)
271 firOpBuilder.setInsertionPoint(atomicCaptureOp);
272 mlir::Value nonAtomicVal;
273 for (auto *nonAtomicSubExpr : nonAtomicSubExprs) {
274 nonAtomicVal = fir::getBase(converter.genExprValue(
275 currentLocation, *nonAtomicSubExpr, nonAtomicStmtCtx));
276 exprValueOverrides.try_emplace(nonAtomicSubExpr, nonAtomicVal);
277 }
278 if (atomicCaptureOp)
279 firOpBuilder.restoreInsertionPoint(insertionPoint);
280 }
281
282 mlir::Operation *atomicUpdateOp = nullptr;
283 if constexpr (std::is_same<AtomicListT,
285 // If no hint clause is specified, the effect is as if
286 // hint(omp_sync_hint_none) had been specified.
287 mlir::IntegerAttr hint = nullptr;
288 mlir::omp::ClauseMemoryOrderKindAttr memoryOrder = nullptr;
289 if (leftHandClauseList)
290 genOmpAtomicHintAndMemoryOrderClauses(converter, *leftHandClauseList,
291 hint, memoryOrder);
292 if (rightHandClauseList)
293 genOmpAtomicHintAndMemoryOrderClauses(converter, *rightHandClauseList,
294 hint, memoryOrder);
295 atomicUpdateOp = firOpBuilder.create<mlir::omp::AtomicUpdateOp>(
296 currentLocation, lhsAddr, hint, memoryOrder);
297 } else {
298 atomicUpdateOp = firOpBuilder.create<mlir::acc::AtomicUpdateOp>(
299 currentLocation, lhsAddr);
300 }
301
302 processOmpAtomicTODO<AtomicListT>(varType, loc);
303
304 llvm::SmallVector<mlir::Type> varTys = {varType};
305 llvm::SmallVector<mlir::Location> locs = {currentLocation};
306 firOpBuilder.createBlock(&atomicUpdateOp->getRegion(0), {}, varTys, locs);
307 mlir::Value val =
308 fir::getBase(atomicUpdateOp->getRegion(0).front().getArgument(0));
309
310 exprValueOverrides.try_emplace(
311 Fortran::semantics::GetExpr(assignmentStmtVariable), val);
312 {
313 // statement context inside the atomic block.
314 converter.overrideExprValues(&exprValueOverrides);
316 mlir::Value rhsExpr = fir::getBase(converter.genExprValue(
317 *Fortran::semantics::GetExpr(assignmentStmtExpr), atomicStmtCtx));
318 mlir::Value convertResult =
319 firOpBuilder.createConvert(currentLocation, varType, rhsExpr);
320 if constexpr (std::is_same<AtomicListT,
322 firOpBuilder.create<mlir::omp::YieldOp>(currentLocation, convertResult);
323 } else {
324 firOpBuilder.create<mlir::acc::YieldOp>(currentLocation, convertResult);
325 }
326 converter.resetExprOverrides();
327 }
328 firOpBuilder.setInsertionPointAfter(atomicUpdateOp);
329}
330
332template <typename AtomicT, typename AtomicListT>
334 const AtomicT &atomicWrite, mlir::Location loc) {
335 const AtomicListT *rightHandClauseList = nullptr;
336 const AtomicListT *leftHandClauseList = nullptr;
337 if constexpr (std::is_same<AtomicListT,
339 // Get the address of atomic read operands.
340 rightHandClauseList = &std::get<2>(atomicWrite.t);
341 leftHandClauseList = &std::get<0>(atomicWrite.t);
342 }
343
345 std::get<Fortran::parser::Statement<Fortran::parser::AssignmentStmt>>(
346 atomicWrite.t)
347 .statement;
348 const Fortran::evaluate::Assignment &assign = *stmt.typedAssignment->v;
350 // Get the value and address of atomic write operands.
351 mlir::Value rhsExpr =
352 fir::getBase(converter.genExprValue(assign.rhs, stmtCtx));
353 mlir::Value lhsAddr =
354 fir::getBase(converter.genExprAddr(assign.lhs, stmtCtx));
355 genOmpAccAtomicWriteStatement(converter, lhsAddr, rhsExpr, leftHandClauseList,
356 rightHandClauseList, loc);
357}
358
360template <typename AtomicT, typename AtomicListT>
362 const AtomicT &atomicRead, mlir::Location loc) {
363 const AtomicListT *rightHandClauseList = nullptr;
364 const AtomicListT *leftHandClauseList = nullptr;
365 if constexpr (std::is_same<AtomicListT,
367 // Get the address of atomic read operands.
368 rightHandClauseList = &std::get<2>(atomicRead.t);
369 leftHandClauseList = &std::get<0>(atomicRead.t);
370 }
371
372 const auto &assignmentStmtExpr = std::get<Fortran::parser::Expr>(
374 atomicRead.t)
375 .statement.t);
376 const auto &assignmentStmtVariable = std::get<Fortran::parser::Variable>(
378 atomicRead.t)
379 .statement.t);
380
382 const Fortran::semantics::SomeExpr &fromExpr =
383 *Fortran::semantics::GetExpr(assignmentStmtExpr);
384 mlir::Type elementType = converter.genType(fromExpr);
385 mlir::Value fromAddress =
386 fir::getBase(converter.genExprAddr(fromExpr, stmtCtx));
387 mlir::Value toAddress = fir::getBase(converter.genExprAddr(
388 *Fortran::semantics::GetExpr(assignmentStmtVariable), stmtCtx));
389 genOmpAccAtomicCaptureStatement(converter, fromAddress, toAddress,
390 leftHandClauseList, rightHandClauseList,
391 elementType, loc);
392}
393
395template <typename AtomicT, typename AtomicListT>
397 const AtomicT &atomicUpdate, mlir::Location loc) {
398 const AtomicListT *rightHandClauseList = nullptr;
399 const AtomicListT *leftHandClauseList = nullptr;
400 if constexpr (std::is_same<AtomicListT,
402 // Get the address of atomic read operands.
403 rightHandClauseList = &std::get<2>(atomicUpdate.t);
404 leftHandClauseList = &std::get<0>(atomicUpdate.t);
405 }
406
407 const auto &assignmentStmtExpr = std::get<Fortran::parser::Expr>(
409 atomicUpdate.t)
410 .statement.t);
411 const auto &assignmentStmtVariable = std::get<Fortran::parser::Variable>(
413 atomicUpdate.t)
414 .statement.t);
415
417 mlir::Value lhsAddr = fir::getBase(converter.genExprAddr(
418 *Fortran::semantics::GetExpr(assignmentStmtVariable), stmtCtx));
419 mlir::Type varType = fir::unwrapRefType(lhsAddr.getType());
420 genOmpAccAtomicUpdateStatement<AtomicListT>(
421 converter, lhsAddr, varType, assignmentStmtVariable, assignmentStmtExpr,
422 leftHandClauseList, rightHandClauseList, loc);
423}
424
426template <typename AtomicT, typename AtomicListT>
428 const AtomicT &atomicConstruct, mlir::Location loc) {
429 const AtomicListT &atomicClauseList =
430 std::get<AtomicListT>(atomicConstruct.t);
431 const auto &assignmentStmtExpr = std::get<Fortran::parser::Expr>(
433 atomicConstruct.t)
434 .statement.t);
435 const auto &assignmentStmtVariable = std::get<Fortran::parser::Variable>(
437 atomicConstruct.t)
438 .statement.t);
440 mlir::Value lhsAddr = fir::getBase(converter.genExprAddr(
441 *Fortran::semantics::GetExpr(assignmentStmtVariable), stmtCtx));
442 mlir::Type varType = fir::unwrapRefType(lhsAddr.getType());
443 // If atomic-clause is not present on the construct, the behaviour is as if
444 // the update clause is specified (for both OpenMP and OpenACC).
445 genOmpAccAtomicUpdateStatement<AtomicListT>(
446 converter, lhsAddr, varType, assignmentStmtVariable, assignmentStmtExpr,
447 &atomicClauseList, nullptr, loc);
448}
449
451template <typename AtomicT, typename AtomicListT>
453 const AtomicT &atomicCapture, mlir::Location loc) {
454 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
455
457 std::get<typename AtomicT::Stmt1>(atomicCapture.t).v.statement;
458 const Fortran::evaluate::Assignment &assign1 = *stmt1.typedAssignment->v;
459 const auto &stmt1Var{std::get<Fortran::parser::Variable>(stmt1.t)};
460 const auto &stmt1Expr{std::get<Fortran::parser::Expr>(stmt1.t)};
462 std::get<typename AtomicT::Stmt2>(atomicCapture.t).v.statement;
463 const Fortran::evaluate::Assignment &assign2 = *stmt2.typedAssignment->v;
464 const auto &stmt2Var{std::get<Fortran::parser::Variable>(stmt2.t)};
465 const auto &stmt2Expr{std::get<Fortran::parser::Expr>(stmt2.t)};
466
467 // Pre-evaluate expressions to be used in the various operations inside
468 // `atomic.capture` since it is not desirable to have anything other than
469 // a `atomic.read`, `atomic.write`, or `atomic.update` operation
470 // inside `atomic.capture`
472 // LHS evaluations are common to all combinations of `atomic.capture`
473 mlir::Value stmt1LHSArg =
474 fir::getBase(converter.genExprAddr(assign1.lhs, stmtCtx));
475 mlir::Value stmt2LHSArg =
476 fir::getBase(converter.genExprAddr(assign2.lhs, stmtCtx));
477
478 // Type information used in generation of `atomic.update` operation
479 mlir::Type stmt1VarType =
480 fir::getBase(converter.genExprValue(assign1.lhs, stmtCtx)).getType();
481 mlir::Type stmt2VarType =
482 fir::getBase(converter.genExprValue(assign2.lhs, stmtCtx)).getType();
483
484 mlir::Operation *atomicCaptureOp = nullptr;
485 if constexpr (std::is_same<AtomicListT,
487 mlir::IntegerAttr hint = nullptr;
488 mlir::omp::ClauseMemoryOrderKindAttr memoryOrder = nullptr;
489 const AtomicListT &rightHandClauseList = std::get<2>(atomicCapture.t);
490 const AtomicListT &leftHandClauseList = std::get<0>(atomicCapture.t);
491 genOmpAtomicHintAndMemoryOrderClauses(converter, leftHandClauseList, hint,
492 memoryOrder);
493 genOmpAtomicHintAndMemoryOrderClauses(converter, rightHandClauseList, hint,
494 memoryOrder);
495 atomicCaptureOp =
496 firOpBuilder.create<mlir::omp::AtomicCaptureOp>(loc, hint, memoryOrder);
497 } else {
498 atomicCaptureOp = firOpBuilder.create<mlir::acc::AtomicCaptureOp>(loc);
499 }
500
501 firOpBuilder.createBlock(&(atomicCaptureOp->getRegion(0)));
502 mlir::Block &block = atomicCaptureOp->getRegion(0).back();
503 firOpBuilder.setInsertionPointToStart(&block);
504 if (Fortran::semantics::checkForSingleVariableOnRHS(stmt1)) {
505 if (Fortran::semantics::checkForSymbolMatch(stmt2)) {
506 // Atomic capture construct is of the form [capture-stmt, update-stmt]
507 const Fortran::semantics::SomeExpr &fromExpr =
508 *Fortran::semantics::GetExpr(stmt1Expr);
509 mlir::Type elementType = converter.genType(fromExpr);
510 genOmpAccAtomicCaptureStatement<AtomicListT>(
511 converter, stmt2LHSArg, stmt1LHSArg,
512 /*leftHandClauseList=*/nullptr,
513 /*rightHandClauseList=*/nullptr, elementType, loc);
514 genOmpAccAtomicUpdateStatement<AtomicListT>(
515 converter, stmt2LHSArg, stmt2VarType, stmt2Var, stmt2Expr,
516 /*leftHandClauseList=*/nullptr,
517 /*rightHandClauseList=*/nullptr, loc, atomicCaptureOp);
518 } else {
519 // Atomic capture construct is of the form [capture-stmt, write-stmt]
520 firOpBuilder.setInsertionPoint(atomicCaptureOp);
521 mlir::Value stmt2RHSArg =
522 fir::getBase(converter.genExprValue(assign2.rhs, stmtCtx));
523 firOpBuilder.setInsertionPointToStart(&block);
524 const Fortran::semantics::SomeExpr &fromExpr =
525 *Fortran::semantics::GetExpr(stmt1Expr);
526 mlir::Type elementType = converter.genType(fromExpr);
527 genOmpAccAtomicCaptureStatement<AtomicListT>(
528 converter, stmt2LHSArg, stmt1LHSArg,
529 /*leftHandClauseList=*/nullptr,
530 /*rightHandClauseList=*/nullptr, elementType, loc);
531 genOmpAccAtomicWriteStatement<AtomicListT>(
532 converter, stmt2LHSArg, stmt2RHSArg,
533 /*leftHandClauseList=*/nullptr,
534 /*rightHandClauseList=*/nullptr, loc);
535 }
536 } else {
537 // Atomic capture construct is of the form [update-stmt, capture-stmt]
538 const Fortran::semantics::SomeExpr &fromExpr =
539 *Fortran::semantics::GetExpr(stmt2Expr);
540 mlir::Type elementType = converter.genType(fromExpr);
541 genOmpAccAtomicUpdateStatement<AtomicListT>(
542 converter, stmt1LHSArg, stmt1VarType, stmt1Var, stmt1Expr,
543 /*leftHandClauseList=*/nullptr,
544 /*rightHandClauseList=*/nullptr, loc, atomicCaptureOp);
545 genOmpAccAtomicCaptureStatement<AtomicListT>(
546 converter, stmt1LHSArg, stmt2LHSArg,
547 /*leftHandClauseList=*/nullptr,
548 /*rightHandClauseList=*/nullptr, elementType, loc);
549 }
550 firOpBuilder.setInsertionPointToEnd(&block);
551 if constexpr (std::is_same<AtomicListT,
553 firOpBuilder.create<mlir::omp::TerminatorOp>(loc);
554 } else {
555 firOpBuilder.create<mlir::acc::TerminatorOp>(loc);
556 }
557 firOpBuilder.setInsertionPointToStart(&block);
558}
559
562template <typename... TerminatorOps>
564 fir::FirOpBuilder &builder,
565 std::list<Fortran::lower::pft::Evaluation> &evaluationList) {
566 mlir::Region *region = &builder.getRegion();
567 for (Fortran::lower::pft::Evaluation &eval : evaluationList) {
568 if (eval.block) {
569 if (eval.block->empty()) {
570 eval.block->erase();
571 eval.block = builder.createBlock(region);
572 } else {
573 [[maybe_unused]] mlir::Operation &terminatorOp = eval.block->back();
574 assert(mlir::isa<TerminatorOps...>(terminatorOp) &&
575 "expected terminator op");
576 }
577 }
578 if (!eval.isDirective() && eval.hasNestedEvaluations())
579 createEmptyRegionBlocks<TerminatorOps...>(builder,
580 eval.getNestedEvaluations());
581 }
582}
583
585getDataOperandBaseAddr(Fortran::lower::AbstractConverter &converter,
586 fir::FirOpBuilder &builder,
587 Fortran::lower::SymbolRef sym, mlir::Location loc) {
588 return fir::factory::getDataOperandBaseAddr(
589 builder, converter.getSymbolAddress(sym),
590 Fortran::semantics::IsOptional(sym), loc);
591}
592
593namespace detail {
594template <typename T> //
595static T &&AsRvalueRef(T &&t) {
596 return std::move(t);
597}
598template <typename T> //
599static T AsRvalueRef(T &t) {
600 return t;
601}
602template <typename T> //
603static T AsRvalueRef(const T &t) {
604 return t;
605}
606
607// Helper class for stripping enclosing parentheses and a conversion that
608// preserves type category. This is used for triplet elements, which are
609// always of type integer(kind=8). The lower/upper bounds are converted to
610// an "index" type, which is 64-bit, so the explicit conversion to kind=8
611// (if present) is not needed. When it's present, though, it causes generated
612// names to contain "int(..., kind=8)".
614 template <Fortran::common::TypeCategory Category, int Kind>
615 static Fortran::semantics::MaybeExpr visit_with_category(
617 &expr) {
618 return Fortran::common::visit(
619 [](auto &&s) { return visit_with_category<Category, Kind>(s); },
620 expr.u);
621 }
622 template <Fortran::common::TypeCategory Category, int Kind>
623 static Fortran::semantics::MaybeExpr visit_with_category(
625 Category> &expr) {
626 return AsGenericExpr(AsRvalueRef(expr.left()));
627 }
628 template <Fortran::common::TypeCategory Category, int Kind, typename T>
629 static Fortran::semantics::MaybeExpr visit_with_category(const T &) {
630 return std::nullopt; //
631 }
632 template <Fortran::common::TypeCategory Category, typename T>
633 static Fortran::semantics::MaybeExpr visit_with_category(const T &) {
634 return std::nullopt; //
635 }
636
637 template <Fortran::common::TypeCategory Category>
638 static Fortran::semantics::MaybeExpr
640 &expr) {
641 return Fortran::common::visit(
642 [](auto &&s) { return visit_with_category<Category>(s); }, expr.u);
643 }
644 static Fortran::semantics::MaybeExpr
646 return Fortran::common::visit([](auto &&s) { return visit(s); }, expr.u);
647 }
648 template <typename T> //
649 static Fortran::semantics::MaybeExpr visit(const T &) {
650 return std::nullopt;
651 }
652};
653
655peelOuterConvert(Fortran::semantics::SomeExpr &expr) {
656 if (auto peeled = PeelConvert::visit(expr))
657 return *peeled;
658 return expr;
659}
660} // namespace detail
661
664template <typename BoundsOp, typename BoundsType>
666genBoundsOps(fir::FirOpBuilder &builder, mlir::Location loc,
669 const std::vector<Fortran::evaluate::Subscript> &subscripts,
670 std::stringstream &asFortran, fir::ExtendedValue &dataExv,
671 bool dataExvIsAssumedSize, fir::factory::AddrAndBoundsInfo &info,
672 bool treatIndexAsSection = false) {
673 int dimension = 0;
674 mlir::Type idxTy = builder.getIndexType();
675 mlir::Type boundTy = builder.getType<BoundsType>();
677
678 mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
679 mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
680 const int dataExvRank = static_cast<int>(dataExv.rank());
681 for (const auto &subscript : subscripts) {
682 const auto *triplet{std::get_if<Fortran::evaluate::Triplet>(&subscript.u)};
683 if (triplet || treatIndexAsSection) {
684 if (dimension != 0)
685 asFortran << ',';
686 mlir::Value lbound, ubound, extent;
687 std::optional<std::int64_t> lval, uval;
688 mlir::Value baseLb =
689 fir::factory::readLowerBound(builder, loc, dataExv, dimension, one);
690 bool defaultLb = baseLb == one;
691 mlir::Value stride = one;
692 bool strideInBytes = false;
693
694 if (mlir::isa<fir::BaseBoxType>(
695 fir::unwrapRefType(info.addr.getType()))) {
696 if (info.isPresent) {
697 stride =
698 builder
699 .genIfOp(loc, idxTy, info.isPresent, /*withElseRegion=*/true)
700 .genThen([&]() {
701 mlir::Value box =
702 !fir::isBoxAddress(info.addr.getType())
703 ? info.addr
704 : builder.create<fir::LoadOp>(loc, info.addr);
705 mlir::Value d =
706 builder.createIntegerConstant(loc, idxTy, dimension);
707 auto dimInfo = builder.create<fir::BoxDimsOp>(
708 loc, idxTy, idxTy, idxTy, box, d);
709 builder.create<fir::ResultOp>(loc, dimInfo.getByteStride());
710 })
711 .genElse([&] {
712 mlir::Value zero =
713 builder.createIntegerConstant(loc, idxTy, 0);
714 builder.create<fir::ResultOp>(loc, zero);
715 })
716 .getResults()[0];
717 } else {
718 mlir::Value box = !fir::isBoxAddress(info.addr.getType())
719 ? info.addr
720 : builder.create<fir::LoadOp>(loc, info.addr);
721 mlir::Value d = builder.createIntegerConstant(loc, idxTy, dimension);
722 auto dimInfo =
723 builder.create<fir::BoxDimsOp>(loc, idxTy, idxTy, idxTy, box, d);
724 stride = dimInfo.getByteStride();
725 }
726 strideInBytes = true;
727 }
728
729 Fortran::semantics::MaybeExpr lower;
730 if (triplet) {
731 lower = Fortran::evaluate::AsGenericExpr(triplet->lower());
732 } else {
733 // Case of IndirectSubscriptIntegerExpr
738 std::get<IndirectSubscriptIntegerExpr>(subscript.u).value();
739 lower = Fortran::evaluate::AsGenericExpr(std::move(oneInt));
740 if (lower->Rank() > 0) {
741 mlir::emitError(
742 loc, "vector subscript cannot be used for an array section");
743 break;
744 }
745 }
746 if (lower) {
747 lval = Fortran::evaluate::ToInt64(*lower);
748 if (lval) {
749 if (defaultLb) {
750 lbound = builder.createIntegerConstant(loc, idxTy, *lval - 1);
751 } else {
752 mlir::Value lb = builder.createIntegerConstant(loc, idxTy, *lval);
753 lbound = builder.create<mlir::arith::SubIOp>(loc, lb, baseLb);
754 }
755 asFortran << *lval;
756 } else {
757 mlir::Value lb =
758 fir::getBase(converter.genExprValue(loc, *lower, stmtCtx));
759 lb = builder.createConvert(loc, baseLb.getType(), lb);
760 lbound = builder.create<mlir::arith::SubIOp>(loc, lb, baseLb);
761 asFortran << detail::peelOuterConvert(*lower).AsFortran();
762 }
763 } else {
764 // If the lower bound is not specified, then the section
765 // starts from offset 0 of the dimension.
766 // Note that the lowerbound in the BoundsOp is always 0-based.
767 lbound = zero;
768 }
769
770 if (!triplet) {
771 // If it is a scalar subscript, then the upper bound
772 // is equal to the lower bound, and the extent is one.
773 ubound = lbound;
774 extent = one;
775 } else {
776 asFortran << ':';
777 Fortran::semantics::MaybeExpr upper =
778 Fortran::evaluate::AsGenericExpr(triplet->upper());
779
780 if (upper) {
781 uval = Fortran::evaluate::ToInt64(*upper);
782 if (uval) {
783 if (defaultLb) {
784 ubound = builder.createIntegerConstant(loc, idxTy, *uval - 1);
785 } else {
786 mlir::Value ub = builder.createIntegerConstant(loc, idxTy, *uval);
787 ubound = builder.create<mlir::arith::SubIOp>(loc, ub, baseLb);
788 }
789 asFortran << *uval;
790 } else {
791 mlir::Value ub =
792 fir::getBase(converter.genExprValue(loc, *upper, stmtCtx));
793 ub = builder.createConvert(loc, baseLb.getType(), ub);
794 ubound = builder.create<mlir::arith::SubIOp>(loc, ub, baseLb);
795 asFortran << detail::peelOuterConvert(*upper).AsFortran();
796 }
797 }
798 if (lower && upper) {
799 if (lval && uval && *uval < *lval) {
800 mlir::emitError(loc, "zero sized array section");
801 break;
802 } else {
803 // Stride is mandatory in evaluate::Triplet. Make sure it's 1.
804 auto val = Fortran::evaluate::ToInt64(triplet->GetStride());
805 if (!val || *val != 1) {
806 mlir::emitError(loc, "stride cannot be specified on "
807 "an array section");
808 break;
809 }
810 }
811 }
812
813 if (info.isPresent && mlir::isa<fir::BaseBoxType>(
814 fir::unwrapRefType(info.addr.getType()))) {
815 extent =
816 builder
817 .genIfOp(loc, idxTy, info.isPresent, /*withElseRegion=*/true)
818 .genThen([&]() {
819 mlir::Value ext = fir::factory::readExtent(
820 builder, loc, dataExv, dimension);
821 builder.create<fir::ResultOp>(loc, ext);
822 })
823 .genElse([&] {
824 mlir::Value zero =
825 builder.createIntegerConstant(loc, idxTy, 0);
826 builder.create<fir::ResultOp>(loc, zero);
827 })
828 .getResults()[0];
829 } else {
830 extent = fir::factory::readExtent(builder, loc, dataExv, dimension);
831 }
832
833 if (dataExvIsAssumedSize && dimension + 1 == dataExvRank) {
834 extent = zero;
835 if (ubound && lbound) {
836 mlir::Value diff =
837 builder.create<mlir::arith::SubIOp>(loc, ubound, lbound);
838 extent = builder.create<mlir::arith::AddIOp>(loc, diff, one);
839 }
840 if (!ubound)
841 ubound = lbound;
842 }
843
844 if (!ubound) {
845 // ub = extent - 1
846 ubound = builder.create<mlir::arith::SubIOp>(loc, extent, one);
847 }
848 }
849 mlir::Value bound = builder.create<BoundsOp>(
850 loc, boundTy, lbound, ubound, extent, stride, strideInBytes, baseLb);
851 bounds.push_back(bound);
852 ++dimension;
853 }
854 }
855 return bounds;
856}
857
858namespace detail {
859template <typename Ref, typename Expr> //
860std::optional<Ref> getRef(Expr &&expr) {
861 if constexpr (std::is_same_v<llvm::remove_cvref_t<Expr>,
863 if (auto *ref = std::get_if<Ref>(&expr.u))
864 return *ref;
865 return std::nullopt;
866 } else {
867 auto maybeRef = Fortran::evaluate::ExtractDataRef(expr);
868 if (!maybeRef || !std::holds_alternative<Ref>(maybeRef->u))
869 return std::nullopt;
870 return std::get<Ref>(maybeRef->u);
871 }
872}
873} // namespace detail
874
875template <typename BoundsOp, typename BoundsType>
876fir::factory::AddrAndBoundsInfo gatherDataOperandAddrAndBounds(
878 semantics::SemanticsContext &semaCtx,
881 const Fortran::semantics::MaybeExpr &maybeDesignator,
882 mlir::Location operandLocation, std::stringstream &asFortran,
883 llvm::SmallVector<mlir::Value> &bounds, bool treatIndexAsSection = false) {
884 using namespace Fortran;
885
887
888 if (!maybeDesignator) {
889 info = getDataOperandBaseAddr(converter, builder, symbol, operandLocation);
890 asFortran << symbol->name().ToString();
891 return info;
892 }
893
894 semantics::SomeExpr designator = *maybeDesignator;
895
896 if ((designator.Rank() > 0 || treatIndexAsSection) &&
897 IsArrayElement(designator)) {
898 auto arrayRef = detail::getRef<evaluate::ArrayRef>(designator);
899 // This shouldn't fail after IsArrayElement(designator).
900 assert(arrayRef && "Expecting ArrayRef");
901
902 fir::ExtendedValue dataExv;
903 bool dataExvIsAssumedSize = false;
904
905 auto toMaybeExpr = [&](auto &&base) {
906 using BaseType = llvm::remove_cvref_t<decltype(base)>;
908
909 if constexpr (std::is_same_v<evaluate::NamedEntity, BaseType>) {
910 if (auto *ref = base.UnwrapSymbolRef())
911 return ea.Designate(evaluate::DataRef{*ref});
912 if (auto *ref = base.UnwrapComponent())
913 return ea.Designate(evaluate::DataRef{*ref});
914 llvm_unreachable("Unexpected NamedEntity");
915 } else {
916 static_assert(std::is_same_v<semantics::SymbolRef, BaseType>);
917 return ea.Designate(evaluate::DataRef{base});
918 }
919 };
920
921 auto arrayBase = toMaybeExpr(arrayRef->base());
922 assert(arrayBase);
923
924 if (detail::getRef<evaluate::Component>(*arrayBase)) {
925 dataExv = converter.genExprAddr(operandLocation, *arrayBase, stmtCtx);
926 info.addr = fir::getBase(dataExv);
927 info.rawInput = info.addr;
928 asFortran << arrayBase->AsFortran();
929 } else {
930 const semantics::Symbol &sym = arrayRef->GetLastSymbol();
931 dataExvIsAssumedSize =
932 Fortran::semantics::IsAssumedSizeArray(sym.GetUltimate());
933 info = getDataOperandBaseAddr(converter, builder, sym, operandLocation);
934 dataExv = converter.getSymbolExtendedValue(sym);
935 asFortran << sym.name().ToString();
936 }
937
938 if (!arrayRef->subscript().empty()) {
939 asFortran << '(';
940 bounds = genBoundsOps<BoundsOp, BoundsType>(
941 builder, operandLocation, converter, stmtCtx, arrayRef->subscript(),
942 asFortran, dataExv, dataExvIsAssumedSize, info, treatIndexAsSection);
943 }
944 asFortran << ')';
945 } else if (auto compRef = detail::getRef<evaluate::Component>(designator)) {
946 fir::ExtendedValue compExv =
947 converter.genExprAddr(operandLocation, designator, stmtCtx);
948 info.addr = fir::getBase(compExv);
949 info.rawInput = info.addr;
950 if (mlir::isa<fir::SequenceType>(fir::unwrapRefType(info.addr.getType())))
951 bounds = fir::factory::genBaseBoundsOps<BoundsOp, BoundsType>(
952 builder, operandLocation, compExv,
953 /*isAssumedSize=*/false);
954 asFortran << designator.AsFortran();
955
956 if (semantics::IsOptional(compRef->GetLastSymbol())) {
957 info.isPresent = builder.create<fir::IsPresentOp>(
958 operandLocation, builder.getI1Type(), info.rawInput);
959 }
960
961 if (auto loadOp =
962 mlir::dyn_cast_or_null<fir::LoadOp>(info.addr.getDefiningOp())) {
963 if (fir::isAllocatableType(loadOp.getType()) ||
964 fir::isPointerType(loadOp.getType())) {
965 info.boxType = info.addr.getType();
966 info.addr = builder.create<fir::BoxAddrOp>(operandLocation, info.addr);
967 }
968 info.rawInput = info.addr;
969 }
970
971 // If the component is an allocatable or pointer the result of
972 // genExprAddr will be the result of a fir.box_addr operation or
973 // a fir.box_addr has been inserted just before.
974 // Retrieve the box so we handle it like other descriptor.
975 if (auto boxAddrOp =
976 mlir::dyn_cast_or_null<fir::BoxAddrOp>(info.addr.getDefiningOp())) {
977 info.addr = boxAddrOp.getVal();
978 info.boxType = info.addr.getType();
979 info.rawInput = info.addr;
980 bounds = fir::factory::genBoundsOpsFromBox<BoundsOp, BoundsType>(
981 builder, operandLocation, compExv, info);
982 }
983 } else {
984 if (detail::getRef<evaluate::ArrayRef>(designator)) {
985 fir::ExtendedValue compExv =
986 converter.genExprAddr(operandLocation, designator, stmtCtx);
987 info.addr = fir::getBase(compExv);
988 info.rawInput = info.addr;
989 asFortran << designator.AsFortran();
990 } else if (auto symRef = detail::getRef<semantics::SymbolRef>(designator)) {
991 // Scalar or full array.
992 fir::ExtendedValue dataExv = converter.getSymbolExtendedValue(*symRef);
993 info =
994 getDataOperandBaseAddr(converter, builder, *symRef, operandLocation);
995 if (mlir::isa<fir::BaseBoxType>(
996 fir::unwrapRefType(info.addr.getType()))) {
997 info.boxType = fir::unwrapRefType(info.addr.getType());
998 bounds = fir::factory::genBoundsOpsFromBox<BoundsOp, BoundsType>(
999 builder, operandLocation, dataExv, info);
1000 }
1001 bool dataExvIsAssumedSize =
1002 Fortran::semantics::IsAssumedSizeArray(symRef->get().GetUltimate());
1003 if (mlir::isa<fir::SequenceType>(fir::unwrapRefType(info.addr.getType())))
1004 bounds = fir::factory::genBaseBoundsOps<BoundsOp, BoundsType>(
1005 builder, operandLocation, dataExv, dataExvIsAssumedSize);
1006 asFortran << symRef->get().name().ToString();
1007 } else { // Unsupported
1008 llvm::report_fatal_error("Unsupported type of OpenACC operand");
1009 }
1010 }
1011
1012 return info;
1013}
1014
1015} // namespace lower
1016} // namespace Fortran
1017
1018#endif // FORTRAN_LOWER_DIRECTIVES_COMMON_H
Definition: indirection.h:72
Definition: expression.h:878
Definition: expression.h:102
Definition: type.h:56
Definition: AbstractConverter.h:82
virtual mlir::Value getSymbolAddress(SymbolRef sym)=0
Get the mlir instance of a symbol.
virtual mlir::Location getCurrentLocation()=0
Get the converter's current location.
virtual mlir::Type genType(const SomeExpr &)=0
Generate the type of an Expr.
virtual fir::ExtendedValue genExprValue(const SomeExpr &expr, StatementContext &context, mlir::Location *locPtr=nullptr)=0
Generate the computations of the expression to produce a value.
virtual void overrideExprValues(const ExprToValueMap *)=0
virtual fir::FirOpBuilder & getFirOpBuilder()=0
Get the OpBuilder.
virtual fir::ExtendedValue genExprAddr(const SomeExpr &expr, StatementContext &context, mlir::Location *locPtr=nullptr)=0
Definition: StatementContext.h:46
Definition: symbol.h:712
Definition: BoxValue.h:478
Definition: FIRBuilder.h:55
mlir::Value createConvert(mlir::Location loc, mlir::Type toTy, mlir::Value val)
Lazy creation of fir.convert op.
Definition: FIRBuilder.cpp:511
IfBuilder genIfOp(mlir::Location loc, mlir::TypeRange results, mlir::Value cdt, bool withElseRegion)
Definition: FIRBuilder.h:463
mlir::Region & getRegion()
Get the current Region of the insertion point.
Definition: FIRBuilder.h:103
mlir::Value createIntegerConstant(mlir::Location loc, mlir::Type integerType, std::int64_t i)
Definition: FIRBuilder.cpp:131
Definition: OpenACC.h:20
llvm::SmallVector< mlir::Value > genBoundsOps(fir::FirOpBuilder &builder, mlir::Location loc, Fortran::lower::AbstractConverter &converter, Fortran::lower::StatementContext &stmtCtx, const std::vector< Fortran::evaluate::Subscript > &subscripts, std::stringstream &asFortran, fir::ExtendedValue &dataExv, bool dataExvIsAssumedSize, fir::factory::AddrAndBoundsInfo &info, bool treatIndexAsSection=false)
Definition: DirectivesCommon.h:666
void genOmpAccAtomicRead(Fortran::lower::AbstractConverter &converter, const AtomicT &atomicRead, mlir::Location loc)
Processes an atomic construct with read clause.
Definition: DirectivesCommon.h:361
void genOmpAtomic(Fortran::lower::AbstractConverter &converter, const AtomicT &atomicConstruct, mlir::Location loc)
Processes an atomic construct with no clause - which implies update clause.
Definition: DirectivesCommon.h:427
void genOmpAccAtomicUpdate(Fortran::lower::AbstractConverter &converter, const AtomicT &atomicUpdate, mlir::Location loc)
Processes an atomic construct with update clause.
Definition: DirectivesCommon.h:396
void createEmptyRegionBlocks(fir::FirOpBuilder &builder, std::list< Fortran::lower::pft::Evaluation > &evaluationList)
Definition: DirectivesCommon.h:563
void genOmpAccAtomicWrite(Fortran::lower::AbstractConverter &converter, const AtomicT &atomicWrite, mlir::Location loc)
Processes an atomic construct with write clause.
Definition: DirectivesCommon.h:333
void genOmpAccAtomicCapture(Fortran::lower::AbstractConverter &converter, const AtomicT &atomicCapture, mlir::Location loc)
Processes an atomic construct with capture clause.
Definition: DirectivesCommon.h:452
Definition: bit-population-count.h:20
mlir::Value readLowerBound(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &box, unsigned dim, mlir::Value defaultValue)
Definition: FIRBuilder.cpp:905
mlir::Value readExtent(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &box, unsigned dim)
Read or get the extent in dimension dim of the array described by box.
Definition: FIRBuilder.cpp:873
bool isBoxAddress(mlir::Type t)
Is t an address to fir.box or class type?
Definition: FIRType.h:475
mlir::Value getBase(const ExtendedValue &exv)
Definition: BoxValue.cpp:21
bool isPointerType(mlir::Type ty)
Definition: FIRType.cpp:273
bool isAllocatableType(mlir::Type ty)
Return true iff ty is the type of an ALLOCATABLE entity or value.
Definition: FIRType.cpp:281
bool isa_trivial(mlir::Type t)
Definition: FIRType.h:202
Definition: idioms.h:61
Definition: expression.h:211
Definition: variable.h:300
Definition: type.h:402
Definition: DirectivesCommon.h:613
Definition: PFTBuilder.h:216
Definition: parse-tree.h:2016
Definition: parse-tree.h:1724
Definition: parse-tree.h:1700
Definition: parse-tree.h:4568
Definition: parse-tree.h:4561
Definition: parse-tree.h:355
Definition: parse-tree.h:1865
Definition: DirectivesCommon.h:31