brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.2 KiB · 3ab8a58 Raw
587 lines · cpp
1//===-- Atomic.cpp -- Lowering of atomic constructs -----------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "Atomic.h"10#include "flang/Evaluate/expression.h"11#include "flang/Evaluate/fold.h"12#include "flang/Evaluate/tools.h"13#include "flang/Evaluate/traverse.h"14#include "flang/Evaluate/type.h"15#include "flang/Lower/AbstractConverter.h"16#include "flang/Lower/OpenMP/Clauses.h"17#include "flang/Lower/PFTBuilder.h"18#include "flang/Lower/StatementContext.h"19#include "flang/Lower/SymbolMap.h"20#include "flang/Optimizer/Builder/FIRBuilder.h"21#include "flang/Optimizer/Builder/Todo.h"22#include "flang/Parser/parse-tree.h"23#include "flang/Semantics/openmp-utils.h"24#include "flang/Semantics/semantics.h"25#include "flang/Semantics/type.h"26#include "flang/Support/Fortran.h"27#include "mlir/Dialect/OpenMP/OpenMPDialect.h"28#include "llvm/ADT/STLExtras.h"29#include "llvm/Support/CommandLine.h"30#include "llvm/Support/raw_ostream.h"31 32#include <optional>33#include <string>34#include <type_traits>35#include <variant>36#include <vector>37 38static llvm::cl::opt<bool> DumpAtomicAnalysis("fdebug-dump-atomic-analysis");39 40using namespace Fortran;41 42// Don't import the entire Fortran::lower.43namespace omp {44using namespace Fortran::lower::omp;45}46 47[[maybe_unused]] static void48dumpAtomicAnalysis(const parser::OpenMPAtomicConstruct::Analysis &analysis) {49  auto whatStr = [](int k) {50    std::string txt = "?";51    switch (k & parser::OpenMPAtomicConstruct::Analysis::Action) {52    case parser::OpenMPAtomicConstruct::Analysis::None:53      txt = "None";54      break;55    case parser::OpenMPAtomicConstruct::Analysis::Read:56      txt = "Read";57      break;58    case parser::OpenMPAtomicConstruct::Analysis::Write:59      txt = "Write";60      break;61    case parser::OpenMPAtomicConstruct::Analysis::Update:62      txt = "Update";63      break;64    }65    switch (k & parser::OpenMPAtomicConstruct::Analysis::Condition) {66    case parser::OpenMPAtomicConstruct::Analysis::IfTrue:67      txt += " | IfTrue";68      break;69    case parser::OpenMPAtomicConstruct::Analysis::IfFalse:70      txt += " | IfFalse";71      break;72    }73    return txt;74  };75 76  auto exprStr = [&](const parser::TypedExpr &expr) {77    if (auto *maybe = expr.get()) {78      if (maybe->v)79        return maybe->v->AsFortran();80    }81    return "<null>"s;82  };83  auto assignStr = [&](const parser::AssignmentStmt::TypedAssignment &assign) {84    if (auto *maybe = assign.get(); maybe && maybe->v) {85      std::string str;86      llvm::raw_string_ostream os(str);87      maybe->v->AsFortran(os);88      return str;89    }90    return "<null>"s;91  };92 93  const semantics::SomeExpr &atom = *analysis.atom.get()->v;94 95  llvm::errs() << "Analysis {\n";96  llvm::errs() << "  atom: " << atom.AsFortran() << "\n";97  llvm::errs() << "  cond: " << exprStr(analysis.cond) << "\n";98  llvm::errs() << "  op0 {\n";99  llvm::errs() << "    what: " << whatStr(analysis.op0.what) << "\n";100  llvm::errs() << "    assign: " << assignStr(analysis.op0.assign) << "\n";101  llvm::errs() << "  }\n";102  llvm::errs() << "  op1 {\n";103  llvm::errs() << "    what: " << whatStr(analysis.op1.what) << "\n";104  llvm::errs() << "    assign: " << assignStr(analysis.op1.assign) << "\n";105  llvm::errs() << "  }\n";106  llvm::errs() << "}\n";107}108 109static bool isPointerAssignment(const evaluate::Assignment &assign) {110  return common::visit(111      common::visitors{112          [](const evaluate::Assignment::BoundsSpec &) { return true; },113          [](const evaluate::Assignment::BoundsRemapping &) { return true; },114          [](const auto &) { return false; },115      },116      assign.u);117}118 119static fir::FirOpBuilder::InsertPoint120getInsertionPointBefore(mlir::Operation *op) {121  return fir::FirOpBuilder::InsertPoint(op->getBlock(),122                                        mlir::Block::iterator(op));123}124 125static fir::FirOpBuilder::InsertPoint126getInsertionPointAfter(mlir::Operation *op) {127  return fir::FirOpBuilder::InsertPoint(op->getBlock(),128                                        ++mlir::Block::iterator(op));129}130 131static mlir::IntegerAttr getAtomicHint(lower::AbstractConverter &converter,132                                       const omp::List<omp::Clause> &clauses) {133  fir::FirOpBuilder &builder = converter.getFirOpBuilder();134  for (const omp::Clause &clause : clauses) {135    if (clause.id != llvm::omp::Clause::OMPC_hint)136      continue;137    auto &hint = std::get<omp::clause::Hint>(clause.u);138    auto maybeVal = evaluate::ToInt64(hint.v);139    CHECK(maybeVal);140    return builder.getI64IntegerAttr(*maybeVal);141  }142  return nullptr;143}144 145static mlir::omp::ClauseMemoryOrderKind146getMemoryOrderKind(common::OmpMemoryOrderType kind) {147  switch (kind) {148  case common::OmpMemoryOrderType::Acq_Rel:149    return mlir::omp::ClauseMemoryOrderKind::Acq_rel;150  case common::OmpMemoryOrderType::Acquire:151    return mlir::omp::ClauseMemoryOrderKind::Acquire;152  case common::OmpMemoryOrderType::Relaxed:153    return mlir::omp::ClauseMemoryOrderKind::Relaxed;154  case common::OmpMemoryOrderType::Release:155    return mlir::omp::ClauseMemoryOrderKind::Release;156  case common::OmpMemoryOrderType::Seq_Cst:157    return mlir::omp::ClauseMemoryOrderKind::Seq_cst;158  }159  llvm_unreachable("Unexpected kind");160}161 162static std::optional<mlir::omp::ClauseMemoryOrderKind>163getMemoryOrderKind(llvm::omp::Clause clauseId) {164  switch (clauseId) {165  case llvm::omp::Clause::OMPC_acq_rel:166    return mlir::omp::ClauseMemoryOrderKind::Acq_rel;167  case llvm::omp::Clause::OMPC_acquire:168    return mlir::omp::ClauseMemoryOrderKind::Acquire;169  case llvm::omp::Clause::OMPC_relaxed:170    return mlir::omp::ClauseMemoryOrderKind::Relaxed;171  case llvm::omp::Clause::OMPC_release:172    return mlir::omp::ClauseMemoryOrderKind::Release;173  case llvm::omp::Clause::OMPC_seq_cst:174    return mlir::omp::ClauseMemoryOrderKind::Seq_cst;175  default:176    return std::nullopt;177  }178}179 180static std::optional<mlir::omp::ClauseMemoryOrderKind>181getMemoryOrderFromRequires(const semantics::Scope &scope) {182  // The REQUIRES construct is only allowed in the main program scope183  // and module scope, but seems like we also accept it in a subprogram184  // scope.185  // For safety, traverse all enclosing scopes and check if their symbol186  // contains REQUIRES.187  const semantics::Scope &unitScope = semantics::omp::GetProgramUnit(scope);188  if (auto *symbol = unitScope.symbol()) {189    const common::OmpMemoryOrderType *admo = common::visit(190        [](auto &&s) {191          using WithOmpDeclarative = semantics::WithOmpDeclarative;192          if constexpr (std::is_convertible_v<decltype(s),193                                              const WithOmpDeclarative &>) {194            return s.ompAtomicDefaultMemOrder();195          }196          return static_cast<const common::OmpMemoryOrderType *>(nullptr);197        },198        symbol->details());199 200    if (admo)201      return getMemoryOrderKind(*admo);202  }203 204  return std::nullopt;205}206 207static std::optional<mlir::omp::ClauseMemoryOrderKind>208getDefaultAtomicMemOrder(semantics::SemanticsContext &semaCtx) {209  unsigned version = semaCtx.langOptions().OpenMPVersion;210  if (version > 50)211    return mlir::omp::ClauseMemoryOrderKind::Relaxed;212  return std::nullopt;213}214 215static std::pair<std::optional<mlir::omp::ClauseMemoryOrderKind>, bool>216getAtomicMemoryOrder(semantics::SemanticsContext &semaCtx,217                     const omp::List<omp::Clause> &clauses,218                     const semantics::Scope &scope) {219  for (const omp::Clause &clause : clauses) {220    if (auto maybeKind = getMemoryOrderKind(clause.id))221      return std::make_pair(*maybeKind, /*canOverride=*/false);222  }223 224  if (auto maybeKind = getMemoryOrderFromRequires(scope))225    return std::make_pair(*maybeKind, /*canOverride=*/true);226 227  return std::make_pair(getDefaultAtomicMemOrder(semaCtx),228                        /*canOverride=*/false);229}230 231static std::optional<mlir::omp::ClauseMemoryOrderKind>232makeValidForAction(std::optional<mlir::omp::ClauseMemoryOrderKind> memOrder,233                   int action0, int action1, unsigned version) {234  // When the atomic default memory order specified on a REQUIRES directive is235  // disallowed on a given ATOMIC operation, and it's not ACQ_REL, the order236  // reverts to RELAXED. ACQ_REL decays to either ACQUIRE or RELEASE, depending237  // on the operation.238 239  if (!memOrder) {240    return memOrder;241  }242 243  using Analysis = parser::OpenMPAtomicConstruct::Analysis;244  // Figure out the main action (i.e. disregard a potential capture operation)245  int action = action0;246  if (action1 != Analysis::None)247    action = action0 == Analysis::Read ? action1 : action0;248 249  // Avaliable orderings: acquire, acq_rel, relaxed, release, seq_cst250 251  if (action == Analysis::Read) {252    // "acq_rel" decays to "acquire"253    if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acq_rel)254      return mlir::omp::ClauseMemoryOrderKind::Acquire;255  } else if (action == Analysis::Write) {256    // "acq_rel" decays to "release"257    if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acq_rel)258      return mlir::omp::ClauseMemoryOrderKind::Release;259  }260 261  if (version > 50) {262    if (action == Analysis::Read) {263      // "release" prohibited264      if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Release)265        return mlir::omp::ClauseMemoryOrderKind::Relaxed;266    }267    if (action == Analysis::Write) {268      // "acquire" prohibited269      if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acquire)270        return mlir::omp::ClauseMemoryOrderKind::Relaxed;271    }272  } else {273    if (action == Analysis::Read) {274      // "release" prohibited275      if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Release)276        return mlir::omp::ClauseMemoryOrderKind::Relaxed;277    } else {278      if (action & Analysis::Write) { // include "update"279        // "acquire" prohibited280        if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acquire)281          return mlir::omp::ClauseMemoryOrderKind::Relaxed;282        if (action == Analysis::Update) {283          // "acq_rel" prohibited284          if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acq_rel)285            return mlir::omp::ClauseMemoryOrderKind::Relaxed;286        }287      }288    }289  }290 291  return memOrder;292}293 294static mlir::omp::ClauseMemoryOrderKindAttr295makeMemOrderAttr(lower::AbstractConverter &converter,296                 std::optional<mlir::omp::ClauseMemoryOrderKind> maybeKind) {297  if (maybeKind) {298    return mlir::omp::ClauseMemoryOrderKindAttr::get(299        converter.getFirOpBuilder().getContext(), *maybeKind);300  }301  return nullptr;302}303 304static mlir::Operation * //305genAtomicRead(lower::AbstractConverter &converter,306              semantics::SemanticsContext &semaCtx, mlir::Location loc,307              lower::StatementContext &stmtCtx, mlir::Value atomAddr,308              const semantics::SomeExpr &atom,309              const evaluate::Assignment &assign, mlir::IntegerAttr hint,310              std::optional<mlir::omp::ClauseMemoryOrderKind> memOrder,311              fir::FirOpBuilder::InsertPoint preAt,312              fir::FirOpBuilder::InsertPoint atomicAt,313              fir::FirOpBuilder::InsertPoint postAt) {314  fir::FirOpBuilder &builder = converter.getFirOpBuilder();315  builder.restoreInsertionPoint(preAt);316 317  // If the atomic clause is read then the memory-order clause must318  // not be release.319  if (memOrder) {320    if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Release) {321      // Reset it back to the default.322      memOrder = getDefaultAtomicMemOrder(semaCtx);323    } else if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acq_rel) {324      // The MLIR verifier doesn't like acq_rel either.325      memOrder = mlir::omp::ClauseMemoryOrderKind::Acquire;326    }327  }328 329  mlir::Value storeAddr =330      fir::getBase(converter.genExprAddr(assign.lhs, stmtCtx, &loc));331  mlir::Type atomType = fir::unwrapRefType(atomAddr.getType());332  mlir::Type storeType = fir::unwrapRefType(storeAddr.getType());333 334  mlir::Value toAddr = [&]() {335    if (atomType == storeType)336      return storeAddr;337    return builder.createTemporary(loc, atomType, ".tmp.atomval");338  }();339 340  builder.restoreInsertionPoint(atomicAt);341  mlir::Operation *op = mlir::omp::AtomicReadOp::create(342      builder, loc, atomAddr, toAddr, mlir::TypeAttr::get(atomType), hint,343      makeMemOrderAttr(converter, memOrder));344 345  if (atomType != storeType) {346    lower::ExprToValueMap overrides;347    // The READ operation could be a part of UPDATE CAPTURE, so make sure348    // we don't emit extra code into the body of the atomic op.349    builder.restoreInsertionPoint(postAt);350    mlir::Value load = fir::LoadOp::create(builder, loc, toAddr);351    overrides.try_emplace(&atom, load);352 353    converter.overrideExprValues(&overrides);354    mlir::Value value =355        fir::getBase(converter.genExprValue(assign.rhs, stmtCtx, &loc));356    converter.resetExprOverrides();357 358    fir::StoreOp::create(builder, loc, value, storeAddr);359  }360  return op;361}362 363static mlir::Operation * //364genAtomicWrite(lower::AbstractConverter &converter,365               semantics::SemanticsContext &semaCtx, mlir::Location loc,366               lower::StatementContext &stmtCtx, mlir::Value atomAddr,367               const semantics::SomeExpr &atom,368               const evaluate::Assignment &assign, mlir::IntegerAttr hint,369               std::optional<mlir::omp::ClauseMemoryOrderKind> memOrder,370               fir::FirOpBuilder::InsertPoint preAt,371               fir::FirOpBuilder::InsertPoint atomicAt,372               fir::FirOpBuilder::InsertPoint postAt) {373  fir::FirOpBuilder &builder = converter.getFirOpBuilder();374  builder.restoreInsertionPoint(preAt);375 376  // If the atomic clause is write then the memory-order clause must377  // not be acquire.378  if (memOrder) {379    if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acquire) {380      // Reset it back to the default.381      memOrder = getDefaultAtomicMemOrder(semaCtx);382    } else if (*memOrder == mlir::omp::ClauseMemoryOrderKind::Acq_rel) {383      // The MLIR verifier doesn't like acq_rel either.384      memOrder = mlir::omp::ClauseMemoryOrderKind::Release;385    }386  }387 388  mlir::Value value =389      fir::getBase(converter.genExprValue(assign.rhs, stmtCtx, &loc));390  mlir::Type atomType = fir::unwrapRefType(atomAddr.getType());391  mlir::Value converted = builder.createConvert(loc, atomType, value);392 393  builder.restoreInsertionPoint(atomicAt);394  mlir::Operation *op =395      mlir::omp::AtomicWriteOp::create(builder, loc, atomAddr, converted, hint,396                                       makeMemOrderAttr(converter, memOrder));397  return op;398}399 400static mlir::Operation *401genAtomicUpdate(lower::AbstractConverter &converter,402                semantics::SemanticsContext &semaCtx, mlir::Location loc,403                lower::StatementContext &stmtCtx, mlir::Value atomAddr,404                const semantics::SomeExpr &atom,405                const evaluate::Assignment &assign, mlir::IntegerAttr hint,406                std::optional<mlir::omp::ClauseMemoryOrderKind> memOrder,407                fir::FirOpBuilder::InsertPoint preAt,408                fir::FirOpBuilder::InsertPoint atomicAt,409                fir::FirOpBuilder::InsertPoint postAt) {410  lower::ExprToValueMap overrides;411  lower::StatementContext naCtx;412  fir::FirOpBuilder &builder = converter.getFirOpBuilder();413  builder.restoreInsertionPoint(preAt);414 415  mlir::Type atomType = fir::unwrapRefType(atomAddr.getType());416 417  // This must exist by now.418  semantics::SomeExpr rhs = assign.rhs;419  semantics::SomeExpr input = *evaluate::GetConvertInput(rhs);420  auto [opcode, args] = evaluate::GetTopLevelOperationIgnoreResizing(input);421  assert(!args.empty() && "Update operation without arguments");422 423  for (auto &arg : args) {424    if (!evaluate::IsSameOrConvertOf(arg, atom)) {425      mlir::Value val = fir::getBase(converter.genExprValue(arg, naCtx, &loc));426      overrides.try_emplace(&arg, val);427    }428  }429 430  mlir::ModuleOp module = builder.getModule();431  mlir::omp::AtomicControlAttr atomicControlAttr =432      mlir::omp::AtomicControlAttr::get(433          builder.getContext(), fir::getAtomicIgnoreDenormalMode(module),434          fir::getAtomicFineGrainedMemory(module),435          fir::getAtomicRemoteMemory(module));436  builder.restoreInsertionPoint(atomicAt);437  auto updateOp = mlir::omp::AtomicUpdateOp::create(438      builder, loc, atomAddr, atomicControlAttr, hint,439      makeMemOrderAttr(converter, memOrder));440 441  mlir::Region &region = updateOp->getRegion(0);442  mlir::Block *block = builder.createBlock(&region, {}, {atomType}, {loc});443  mlir::Value localAtom = fir::getBase(block->getArgument(0));444  overrides.try_emplace(&atom, localAtom);445 446  converter.overrideExprValues(&overrides);447  mlir::Value updated =448      fir::getBase(converter.genExprValue(rhs, stmtCtx, &loc));449  mlir::Value converted = builder.createConvert(loc, atomType, updated);450  mlir::omp::YieldOp::create(builder, loc, converted);451  converter.resetExprOverrides();452 453  builder.restoreInsertionPoint(postAt); // For naCtx cleanups454  return updateOp;455}456 457static mlir::Operation *458genAtomicOperation(lower::AbstractConverter &converter,459                   semantics::SemanticsContext &semaCtx, mlir::Location loc,460                   lower::StatementContext &stmtCtx, int action,461                   mlir::Value atomAddr, const semantics::SomeExpr &atom,462                   const evaluate::Assignment &assign, mlir::IntegerAttr hint,463                   std::optional<mlir::omp::ClauseMemoryOrderKind> memOrder,464                   fir::FirOpBuilder::InsertPoint preAt,465                   fir::FirOpBuilder::InsertPoint atomicAt,466                   fir::FirOpBuilder::InsertPoint postAt) {467  if (isPointerAssignment(assign)) {468    TODO(loc, "Code generation for pointer assignment is not implemented yet");469  }470 471  // This function and the functions called here do not preserve the472  // builder's insertion point, or set it to anything specific.473  switch (action) {474  case parser::OpenMPAtomicConstruct::Analysis::Read:475    return genAtomicRead(converter, semaCtx, loc, stmtCtx, atomAddr, atom,476                         assign, hint, memOrder, preAt, atomicAt, postAt);477  case parser::OpenMPAtomicConstruct::Analysis::Write:478    return genAtomicWrite(converter, semaCtx, loc, stmtCtx, atomAddr, atom,479                          assign, hint, memOrder, preAt, atomicAt, postAt);480  case parser::OpenMPAtomicConstruct::Analysis::Update:481    return genAtomicUpdate(converter, semaCtx, loc, stmtCtx, atomAddr, atom,482                           assign, hint, memOrder, preAt, atomicAt, postAt);483  default:484    return nullptr;485  }486}487 488void Fortran::lower::omp::lowerAtomic(489    AbstractConverter &converter, SymMap &symTable,490    semantics::SemanticsContext &semaCtx, pft::Evaluation &eval,491    const parser::OpenMPAtomicConstruct &construct) {492  auto get = [](auto &&typedWrapper) -> decltype(&*typedWrapper.get()->v) {493    if (auto *maybe = typedWrapper.get(); maybe && maybe->v) {494      return &*maybe->v;495    } else {496      return nullptr;497    }498  };499 500  fir::FirOpBuilder &builder = converter.getFirOpBuilder();501  const parser::OmpDirectiveSpecification &dirSpec = construct.BeginDir();502  omp::List<omp::Clause> clauses = makeClauses(dirSpec.Clauses(), semaCtx);503  lower::StatementContext stmtCtx;504 505  const parser::OpenMPAtomicConstruct::Analysis &analysis = construct.analysis;506  if (DumpAtomicAnalysis)507    dumpAtomicAnalysis(analysis);508 509  const semantics::SomeExpr &atom = *get(analysis.atom);510  mlir::Location loc = converter.genLocation(construct.source);511  mlir::Value atomAddr =512      fir::getBase(converter.genExprAddr(atom, stmtCtx, &loc));513  mlir::IntegerAttr hint = getAtomicHint(converter, clauses);514  auto [memOrder, canOverride] = getAtomicMemoryOrder(515      semaCtx, clauses, semaCtx.FindScope(construct.source));516 517  unsigned version = semaCtx.langOptions().OpenMPVersion;518  int action0 = analysis.op0.what & analysis.Action;519  int action1 = analysis.op1.what & analysis.Action;520  if (canOverride)521    memOrder = makeValidForAction(memOrder, action0, action1, version);522 523  if (auto *cond = get(analysis.cond)) {524    (void)cond;525    TODO(loc, "OpenMP ATOMIC COMPARE");526  } else {527    mlir::Operation *captureOp = nullptr;528    fir::FirOpBuilder::InsertPoint preAt = builder.saveInsertionPoint();529    fir::FirOpBuilder::InsertPoint atomicAt, postAt;530 531    if (construct.IsCapture()) {532      // Capturing operation.533      assert(action0 != analysis.None && action1 != analysis.None &&534             "Expexcing two actions");535      (void)action0;536      (void)action1;537      captureOp = mlir::omp::AtomicCaptureOp::create(538          builder, loc, hint, makeMemOrderAttr(converter, memOrder));539      // Set the non-atomic insertion point to before the atomic.capture.540      preAt = getInsertionPointBefore(captureOp);541 542      mlir::Block *block = builder.createBlock(&captureOp->getRegion(0));543      builder.setInsertionPointToEnd(block);544      // Set the atomic insertion point to before the terminator inside545      // atomic.capture.546      mlir::Operation *term = mlir::omp::TerminatorOp::create(builder, loc);547      atomicAt = getInsertionPointBefore(term);548      postAt = getInsertionPointAfter(captureOp);549      hint = nullptr;550      memOrder = std::nullopt;551    } else {552      // Non-capturing operation.553      assert(action0 != analysis.None && action1 == analysis.None &&554             "Expexcing single action");555      assert(!(analysis.op0.what & analysis.Condition));556      postAt = atomicAt = preAt;557    }558 559    // The builder's insertion point needs to be specifically set before560    // each call to `genAtomicOperation`.561    mlir::Operation *firstOp = genAtomicOperation(562        converter, semaCtx, loc, stmtCtx, analysis.op0.what, atomAddr, atom,563        *get(analysis.op0.assign), hint, memOrder, preAt, atomicAt, postAt);564    assert(firstOp && "Should have created an atomic operation");565    atomicAt = getInsertionPointAfter(firstOp);566 567    mlir::Operation *secondOp = nullptr;568    if (analysis.op1.what != analysis.None) {569      secondOp = genAtomicOperation(570          converter, semaCtx, loc, stmtCtx, analysis.op1.what, atomAddr, atom,571          *get(analysis.op1.assign), hint, memOrder, preAt, atomicAt, postAt);572    }573 574    if (construct.IsCapture()) {575      // If this is a capture operation, the first/second ops will be inside576      // of it. Set the insertion point to past the capture op itself.577      builder.restoreInsertionPoint(postAt);578    } else {579      if (secondOp) {580        builder.setInsertionPointAfter(secondOp);581      } else {582        builder.setInsertionPointAfter(firstOp);583      }584    }585  }586}587