diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -169,9 +169,8 @@ def LLVM_FNegOp : LLVM_UnaryFloatArithmeticOp< LLVM_ScalarOrVectorOf, "fneg", "FNeg">; -// Common code definition that is used to verify and set the alignment attribute -// of LLVM ops that accept such an attribute. -class MemoryOpWithAlignmentBase { +// Common code definitions used to set memory operation attributes and flags. +class MemoryOpBase { code setAlignmentCode = [{ if ($alignment.has_value()) { auto align = *$alignment; @@ -179,10 +178,15 @@ inst->setAlignment(llvm::Align(align)); } }]; -} - -// Code definition that is used for nontemporal metadata creation. -class MemoryOpWithAlignmentAndAttributes : MemoryOpWithAlignmentBase { + code setVolatileCode = [{ + inst->setVolatile($volatile_); + }]; + code setSyncScopeCode = [{ + if ($syncscope.has_value()) { + llvm::LLVMContext &llvmContext = builder.getContext(); + inst->setSyncScopeID(llvmContext.getOrInsertSyncScopeID(*$syncscope)); + } + }]; code setNonTemporalMetadataCode = [{ if ($nontemporal) { llvm::Module *module = builder.GetInsertBlock()->getModule(); @@ -192,22 +196,19 @@ inst->setMetadata(module->getMDKindID("nontemporal"), metadata); } }]; - code setAccessGroupsMetadataCode = [{ moduleTranslation.setAccessGroupsMetadata(op, inst); }]; - code setAliasScopeMetadataCode = [{ moduleTranslation.setAliasScopeMetadata(op, inst); }]; - code setTBAAMetadataCode = [{ moduleTranslation.setTBAAMetadata(op, inst); }]; } // Memory-related operations. -def LLVM_AllocaOp : LLVM_Op<"alloca">, MemoryOpWithAlignmentBase { +def LLVM_AllocaOp : LLVM_Op<"alloca">, MemoryOpBase { let arguments = (ins AnyInteger:$arraySize, OptionalAttr:$alignment, OptionalAttr:$elem_type); @@ -344,7 +345,7 @@ let hasVerifier = 1; } -def LLVM_LoadOp : LLVM_Op<"load">, MemoryOpWithAlignmentAndAttributes { +def LLVM_LoadOp : LLVM_Op<"load">, MemoryOpBase { let arguments = (ins Arg, "", [MemRead]>:$addr, OptionalAttr:$access_groups, OptionalAttr:$alias_scopes, @@ -383,7 +384,7 @@ let hasVerifier = 1; } -def LLVM_StoreOp : LLVM_Op<"store">, MemoryOpWithAlignmentAndAttributes { +def LLVM_StoreOp : LLVM_Op<"store">, MemoryOpBase { let arguments = (ins LLVM_LoadableType:$value, Arg,"",[MemWrite]>:$addr, OptionalAttr:$access_groups, @@ -1655,79 +1656,111 @@ def LLVM_AtomicRMWType : AnyTypeOf<[LLVM_AnyFloat, AnyInteger]>; -// FIXME: Need to add alignment and syncscope attribute to MLIR atomicrmw -// operation. def LLVM_AtomicRMWOp : LLVM_Op<"atomicrmw", [ TypesMatchWith<"result #0 and operand #1 have the same type", - "val", "res", "$_self">]> { + "val", "res", "$_self">]>, MemoryOpBase { let arguments = (ins AtomicBinOp:$bin_op, LLVM_PointerTo:$ptr, - LLVM_AtomicRMWType:$val, AtomicOrdering:$ordering); + LLVM_AtomicRMWType:$val, AtomicOrdering:$ordering, + OptionalAttr:$syncscope, + OptionalAttr:$alignment, + UnitAttr:$volatile_); let results = (outs LLVM_AtomicRMWType:$res); - let hasVerifier = 1; let assemblyFormat = [{ - $bin_op $ptr `,` $val $ordering - attr-dict `:` qualified(type($ptr)) `,` type($val) + (`volatile` $volatile_^)? $bin_op $ptr `,` $val + (`syncscope` `(` $syncscope^ `)`)? $ordering attr-dict `:` + qualified(type($ptr)) `,` type($val) }]; string llvmInstName = "AtomicRMW"; string llvmBuilder = [{ - $res = builder.CreateAtomicRMW( + auto *inst = builder.CreateAtomicRMW( convertAtomicBinOpToLLVM($bin_op), $ptr, $val, llvm::MaybeAlign(), convertAtomicOrderingToLLVM($ordering)); - }]; + $res = inst; + }] # setVolatileCode + # setSyncScopeCode + # setAlignmentCode; string mlirBuilder = [{ auto *atomicInst = cast(inst); - $res = $_builder.create($_location, $_resultType, + unsigned alignment = atomicInst->getAlign().value(); + $res = $_builder.create($_location, convertAtomicBinOpFromLLVM(atomicInst->getOperation()), $ptr, $val, - convertAtomicOrderingFromLLVM(atomicInst->getOrdering())); + convertAtomicOrderingFromLLVM(atomicInst->getOrdering()), + getLLVMSyncScope(atomicInst), alignment, atomicInst->isVolatile()); }]; - // Only $ptr and $val are llvm instruction operands. - list llvmArgIndices = [-1, 0, 1, -1]; + list llvmArgIndices = [-1, 0, 1, -1, -1, -1, -1]; + let builders = [ + OpBuilder<(ins "LLVM::AtomicBinOp":$binOp, "Value":$ptr, "Value":$val, + "LLVM::AtomicOrdering":$ordering, + CArg<"StringRef", "StringRef()">:$syncscope, + CArg<"unsigned", "0">:$alignment, CArg<"bool", "false">:$isVolatile + )> + ]; + let hasVerifier = 1; } def LLVM_AtomicCmpXchgType : AnyTypeOf<[AnyInteger, LLVM_AnyPointer]>; -// FIXME: Need to add alignment attribute to MLIR cmpxchg operation. def LLVM_AtomicCmpXchgOp : LLVM_Op<"cmpxchg", [ TypesMatchWith<"operand #1 and operand #2 have the same type", "val", "cmp", "$_self">, TypesMatchWith<"result #0 has an LLVM struct type consisting of " - "the type of operand #2 and a bool", - "val", "res", "getValAndBoolStructType($_self)">]> { + "the type of operand #2 and a bool", "val", "res", + "getValAndBoolStructType($_self)">]>, MemoryOpBase { let arguments = (ins LLVM_PointerTo:$ptr, LLVM_AtomicCmpXchgType:$cmp, LLVM_AtomicCmpXchgType:$val, AtomicOrdering:$success_ordering, - AtomicOrdering:$failure_ordering); + AtomicOrdering:$failure_ordering, + OptionalAttr:$syncscope, + OptionalAttr:$alignment, + UnitAttr:$weak, + UnitAttr:$volatile_); let results = (outs LLVM_AnyStruct:$res); - let hasVerifier = 1; let assemblyFormat = [{ - $ptr `,` $cmp `,` $val $success_ordering $failure_ordering + (`weak` $weak^)? (`volatile` $volatile_^)? $ptr `,` $cmp `,` $val + (`syncscope` `(` $syncscope^ `)`)? $success_ordering $failure_ordering attr-dict `:` qualified(type($ptr)) `,` type($val) }]; string llvmInstName = "AtomicCmpXchg"; string llvmBuilder = [{ - $res = builder.CreateAtomicCmpXchg($ptr, $cmp, $val, llvm::MaybeAlign(), - convertAtomicOrderingToLLVM($success_ordering), - convertAtomicOrderingToLLVM($failure_ordering)); - }]; + auto *inst = builder.CreateAtomicCmpXchg($ptr, $cmp, $val, + llvm::MaybeAlign(), convertAtomicOrderingToLLVM($success_ordering), + convertAtomicOrderingToLLVM($failure_ordering)); + $res = inst; + inst->setWeak($weak); + }] # setVolatileCode + # setSyncScopeCode + # setAlignmentCode; string mlirBuilder = [{ auto *cmpXchgInst = cast(inst); + unsigned alignment = cmpXchgInst->getAlign().value(); $res = $_builder.create( - $_location, $_resultType, $ptr, $cmp, $val, + $_location, $ptr, $cmp, $val, convertAtomicOrderingFromLLVM(cmpXchgInst->getSuccessOrdering()), - convertAtomicOrderingFromLLVM(cmpXchgInst->getFailureOrdering())); + convertAtomicOrderingFromLLVM(cmpXchgInst->getFailureOrdering()), + getLLVMSyncScope(cmpXchgInst), alignment, cmpXchgInst->isWeak(), + cmpXchgInst->isVolatile()); }]; + let builders = [ + OpBuilder<(ins "Value":$ptr, "Value":$cmp, "Value":$val, + "LLVM::AtomicOrdering":$successOrdering, + "LLVM::AtomicOrdering":$failureOrdering, + CArg<"StringRef", "StringRef()">:$syncscope, + CArg<"unsigned", "0">:$alignment, CArg<"bool", "false">:$isWeak, + CArg<"bool", "false">:$isVolatile + )> + ]; + let hasVerifier = 1; } -def LLVM_FenceOp : LLVM_Op<"fence"> { - let arguments = (ins AtomicOrdering:$ordering, StrAttr:$syncscope); - let builders = [LLVM_VoidResultTypeOpBuilder, LLVM_ZeroResultOpBuilder]; +def LLVM_FenceOp : LLVM_Op<"fence">, MemoryOpBase { + let arguments = (ins AtomicOrdering:$ordering, + OptionalAttr:$syncscope); + let assemblyFormat = "(`syncscope` `(` $syncscope^ `)`)? $ordering attr-dict"; string llvmInstName = "Fence"; let llvmBuilder = [{ - llvm::LLVMContext &llvmContext = builder.getContext(); - builder.CreateFence(convertAtomicOrderingToLLVM($ordering), - llvmContext.getOrInsertSyncScopeID($syncscope)); - }]; + auto *inst = builder.CreateFence(convertAtomicOrderingToLLVM($ordering)); + }] # setSyncScopeCode; string mlirBuilder = [{ llvm::FenceInst *fenceInst = cast(inst); $_op = $_builder.create( @@ -1735,7 +1768,12 @@ convertAtomicOrderingFromLLVM(fenceInst->getOrdering()), getLLVMSyncScope(fenceInst)); }]; - let hasCustomAssemblyFormat = 1; + let builders = [ + LLVM_VoidResultTypeOpBuilder, + LLVM_ZeroResultOpBuilder, + OpBuilder<(ins "LLVM::AtomicOrdering":$ordering, + CArg<"StringRef", "StringRef()">:$syncscope)> + ]; let hasVerifier = 1; } diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp --- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp +++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp @@ -588,12 +588,8 @@ // Append the cmpxchg op to the end of the loop block. auto successOrdering = LLVM::AtomicOrdering::acq_rel; auto failureOrdering = LLVM::AtomicOrdering::monotonic; - auto boolType = IntegerType::get(rewriter.getContext(), 1); - auto pairType = LLVM::LLVMStructType::getLiteral(rewriter.getContext(), - {valueType, boolType}); auto cmpxchg = rewriter.create( - loc, pairType, dataPtr, loopArgument, result, successOrdering, - failureOrdering); + loc, dataPtr, loopArgument, result, successOrdering, failureOrdering); // Extract the %new_loaded and %ok values from the pair. Value newLoaded = rewriter.create(loc, cmpxchg, 0); Value ok = rewriter.create(loc, cmpxchg, 1); @@ -1671,13 +1667,12 @@ auto maybeKind = matchSimpleAtomicOp(atomicOp); if (!maybeKind) return failure(); - auto resultType = adaptor.getValue().getType(); auto memRefType = atomicOp.getMemRefType(); auto dataPtr = getStridedElementPtr(atomicOp.getLoc(), memRefType, adaptor.getMemref(), adaptor.getIndices(), rewriter); rewriter.replaceOpWithNewOp( - atomicOp, resultType, *maybeKind, dataPtr, adaptor.getValue(), + atomicOp, *maybeKind, dataPtr, adaptor.getValue(), LLVM::AtomicOrdering::acq_rel); return success(); } diff --git a/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp b/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp --- a/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp +++ b/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp @@ -229,7 +229,7 @@ builder.setInsertionPointToEnd(atomicBlock); Value loaded = builder.create(reduce.getLoc(), atomicBlock->getArgument(1)); - builder.create(reduce.getLoc(), type, atomicKind, + builder.create(reduce.getLoc(), atomicKind, atomicBlock->getArgument(0), loaded, LLVM::AtomicOrdering::monotonic); builder.create(reduce.getLoc(), ArrayRef()); diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp --- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp +++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp @@ -2227,40 +2227,18 @@ OpFoldResult LLVM::ConstantOp::fold(FoldAdaptor) { return getValue(); } //===----------------------------------------------------------------------===// -// Utility functions for parsing atomic ops -//===----------------------------------------------------------------------===// - -// Helper function to parse a keyword into the specified attribute named by -// `attrName`. The keyword must match one of the string values defined by the -// AtomicOrdering enum. The resulting I64 attribute is added to the `result` -// state. -static ParseResult parseAtomicOrdering(OpAsmParser &parser, - OperationState &result, - StringRef attrName) { - SMLoc loc; - StringRef ordering; - if (parser.getCurrentLocation(&loc) || parser.parseKeyword(&ordering)) - return failure(); - - // Replace the keyword `ordering` with an integer attribute. - auto kind = symbolizeAtomicOrdering(ordering); - if (!kind) { - return parser.emitError(loc) - << "'" << ordering << "' is an incorrect value of the '" << attrName - << "' attribute"; - } - - auto value = static_cast(*kind); - auto attr = parser.getBuilder().getI64IntegerAttr(value); - result.addAttribute(attrName, attr); +// AtomicRMWOp +//===----------------------------------------------------------------------===// - return success(); +void AtomicRMWOp::build(OpBuilder &builder, OperationState &state, + AtomicBinOp binOp, Value ptr, Value val, + AtomicOrdering ordering, StringRef syncscope, + unsigned alignment, bool isVolatile) { + build(builder, state, val.getType(), binOp, ptr, val, ordering, + !syncscope.empty() ? builder.getStringAttr(syncscope) : nullptr, + alignment ? builder.getI64IntegerAttr(alignment) : nullptr, isVolatile); } -//===----------------------------------------------------------------------===// -// Verifier for LLVM::AtomicRMWOp. -//===----------------------------------------------------------------------===// - LogicalResult AtomicRMWOp::verify() { auto ptrType = getPtr().getType().cast(); auto valType = getVal().getType(); @@ -2297,7 +2275,7 @@ } //===----------------------------------------------------------------------===// -// Verifier for LLVM::AtomicCmpXchgOp. +// AtomicCmpXchgOp //===----------------------------------------------------------------------===// /// Returns an LLVM struct type that contains a value type and a boolean type. @@ -2306,6 +2284,18 @@ return LLVMStructType::getLiteral(valType.getContext(), {valType, boolType}); } +void AtomicCmpXchgOp::build(OpBuilder &builder, OperationState &state, + Value ptr, Value cmp, Value val, + AtomicOrdering successOrdering, + AtomicOrdering failureOrdering, StringRef syncscope, + unsigned alignment, bool isWeak, bool isVolatile) { + build(builder, state, getValAndBoolStructType(val.getType()), ptr, cmp, val, + successOrdering, failureOrdering, + !syncscope.empty() ? builder.getStringAttr(syncscope) : nullptr, + alignment ? builder.getI64IntegerAttr(alignment) : nullptr, isWeak, + isVolatile); +} + LogicalResult AtomicCmpXchgOp::verify() { auto ptrType = getPtr().getType().cast(); if (!ptrType) @@ -2331,35 +2321,13 @@ } //===----------------------------------------------------------------------===// -// Printer, parser and verifier for LLVM::FenceOp. +// FenceOp //===----------------------------------------------------------------------===// -// ::= `llvm.fence` (`syncscope(`strAttr`)`)? keyword -// attribute-dict? -ParseResult FenceOp::parse(OpAsmParser &parser, OperationState &result) { - StringAttr sScope; - StringRef syncscopeKeyword = "syncscope"; - if (!failed(parser.parseOptionalKeyword(syncscopeKeyword))) { - if (parser.parseLParen() || - parser.parseAttribute(sScope, syncscopeKeyword, result.attributes) || - parser.parseRParen()) - return failure(); - } else { - result.addAttribute(syncscopeKeyword, - parser.getBuilder().getStringAttr("")); - } - if (parseAtomicOrdering(parser, result, "ordering") || - parser.parseOptionalAttrDict(result.attributes)) - return failure(); - return success(); -} - -void FenceOp::print(OpAsmPrinter &p) { - StringRef syncscopeKeyword = "syncscope"; - p << ' '; - if (!(*this)->getAttr(syncscopeKeyword).cast().getValue().empty()) - p << "syncscope(" << (*this)->getAttr(syncscopeKeyword) << ") "; - p << stringifyAtomicOrdering(getOrdering()); +void FenceOp::build(OpBuilder &builder, OperationState &state, + AtomicOrdering ordering, StringRef syncscope) { + build(builder, state, ordering, + syncscope.empty() ? nullptr : builder.getStringAttr(syncscope)); } LogicalResult FenceOp::verify() { diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp --- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp +++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp @@ -98,15 +98,27 @@ } } -/// Converts the sync scope identifier of `fenceInst` to the string -/// representation necessary to build the LLVM dialect fence operation. -static StringRef getLLVMSyncScope(llvm::FenceInst *fenceInst) { - llvm::LLVMContext &llvmContext = fenceInst->getContext(); - SmallVector syncScopeNames; - llvmContext.getSyncScopeNames(syncScopeNames); - for (StringRef name : syncScopeNames) - if (fenceInst->getSyncScopeID() == llvmContext.getOrInsertSyncScopeID(name)) - return name; +/// Converts the sync scope identifier of `inst` to the string representation +/// necessary to build an atomic LLVM dialect operation. Returns the empty +/// string if the operation has either no sync scope or the default system-level +/// sync scope attached. The atomic operations only set their sync scope +/// attribute if they have a non-default sync scope attached. +static StringRef getLLVMSyncScope(llvm::Instruction *inst) { + std::optional syncScopeID = + llvm::getAtomicSyncScopeID(inst); + if (!syncScopeID) + return ""; + + // Search the sync scope name for the given identifier. The default + // system-level sync scope thereby maps to the empty string. + SmallVector syncScopeName; + llvm::LLVMContext &llvmContext = inst->getContext(); + llvmContext.getSyncScopeNames(syncScopeName); + auto *it = llvm::find_if(syncScopeName, [&](StringRef name) { + return *syncScopeID == llvmContext.getOrInsertSyncScopeID(name); + }); + if (it != syncScopeName.end()) + return *it; llvm_unreachable("incorrect sync scope identifier"); } diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir --- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir +++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir @@ -343,6 +343,8 @@ func.func @atomicrmw(%ptr : !llvm.ptr, %val : f32) { // CHECK: llvm.atomicrmw fadd %{{.*}}, %{{.*}} monotonic : !llvm.ptr, f32 %0 = llvm.atomicrmw fadd %ptr, %val monotonic : !llvm.ptr, f32 + // CHECK: llvm.atomicrmw volatile fsub %{{.*}}, %{{.*}} syncscope("singlethread") monotonic {alignment = 16 : i64} : !llvm.ptr, f32 + %1 = llvm.atomicrmw volatile fsub %ptr, %val syncscope("singlethread") monotonic {alignment = 16 : i64} : !llvm.ptr, f32 llvm.return } @@ -350,6 +352,8 @@ func.func @cmpxchg(%ptr : !llvm.ptr, %cmp : i32, %new : i32) { // CHECK: llvm.cmpxchg %{{.*}}, %{{.*}}, %{{.*}} acq_rel monotonic : !llvm.ptr, i32 %0 = llvm.cmpxchg %ptr, %cmp, %new acq_rel monotonic : !llvm.ptr, i32 + // CHECK: llvm.cmpxchg weak volatile %{{.*}}, %{{.*}}, %{{.*}} syncscope("singlethread") acq_rel monotonic {alignment = 16 : i64} : !llvm.ptr, i32 + %1 = llvm.cmpxchg weak volatile %ptr, %cmp, %new syncscope("singlethread") acq_rel monotonic {alignment = 16 : i64} : !llvm.ptr, i32 llvm.return } diff --git a/mlir/test/Target/LLVMIR/Import/instructions.ll b/mlir/test/Target/LLVMIR/Import/instructions.ll --- a/mlir/test/Target/LLVMIR/Import/instructions.ll +++ b/mlir/test/Target/LLVMIR/Import/instructions.ll @@ -401,6 +401,11 @@ %16 = atomicrmw uinc_wrap ptr %ptr1, i32 %val1 acquire ; CHECK: llvm.atomicrmw udec_wrap %[[PTR1]], %[[VAL1]] acquire %17 = atomicrmw udec_wrap ptr %ptr1, i32 %val1 acquire + + ; CHECK: llvm.atomicrmw volatile + ; CHECK-SAME: syncscope("singlethread") + ; CHECK-SAME: {alignment = 8 : i64} + %18 = atomicrmw volatile udec_wrap ptr %ptr1, i32 %val1 syncscope("singlethread") acquire, align 8 ret void } @@ -415,6 +420,11 @@ %1 = cmpxchg ptr %ptr1, i32 %val1, i32 %val2 seq_cst seq_cst ; CHECK: llvm.cmpxchg %[[PTR1]], %[[VAL1]], %[[VAL2]] monotonic seq_cst %2 = cmpxchg ptr %ptr1, i32 %val1, i32 %val2 monotonic seq_cst + + ; CHECK: llvm.cmpxchg weak volatile + ; CHECK-SAME: syncscope("singlethread") + ; CHECK-SAME: {alignment = 8 : i64} + %3 = cmpxchg weak volatile ptr %ptr1, i32 %val1, i32 %val2 syncscope("singlethread") monotonic seq_cst, align 8 ret void } diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir --- a/mlir/test/Target/LLVMIR/llvmir.mlir +++ b/mlir/test/Target/LLVMIR/llvmir.mlir @@ -1436,6 +1436,11 @@ %15 = llvm.atomicrmw uinc_wrap %i32_ptr, %i32 monotonic : !llvm.ptr, i32 // CHECK: atomicrmw udec_wrap ptr %{{.*}}, i32 %{{.*}} monotonic %16 = llvm.atomicrmw udec_wrap %i32_ptr, %i32 monotonic : !llvm.ptr, i32 + + // CHECK: atomicrmw volatile + // CHECK-SAME: syncscope("singlethread") + // CHECK-SAME: align 8 + %17 = llvm.atomicrmw volatile udec_wrap %i32_ptr, %i32 syncscope("singlethread") monotonic {alignment = 8 : i64} : !llvm.ptr, i32 llvm.return } @@ -1447,6 +1452,11 @@ %1 = llvm.extractvalue %0[0] : !llvm.struct<(i32, i1)> // CHECK: %{{[0-9]+}} = extractvalue { i32, i1 } %{{[0-9]+}}, 1 %2 = llvm.extractvalue %0[1] : !llvm.struct<(i32, i1)> + + // CHECK: cmpxchg weak volatile + // CHECK-SAME: syncscope("singlethread") + // CHECK-SAME: align 8 + %3 = llvm.cmpxchg weak volatile %ptr, %cmp, %val syncscope("singlethread") acq_rel monotonic {alignment = 8 : i64} : !llvm.ptr, i32 llvm.return }