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 ®ion = updateOp->getRegion(0);442 mlir::Block *block = builder.createBlock(®ion, {}, {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