From f9173c2958bf6cb9bf23cb00184ce91dfba6acbc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Markus=20B=C3=B6ck?= Date: Wed, 19 Jul 2023 16:35:54 +0200 Subject: [PATCH] [mlir][LLVM] Convert `noalias` parameters into alias scopes during inlining Currently, inlining a function with a `noalias` parameter leads to a large loss of optimization potential as the `noalias` parameter, an important hint for alias analysis, is lost completely. This patch fixes this with the same approach as LLVM by annotating all users of the `noalias` parameter with appropriate alias and noalias scope lists. The implementation done here is not as sophisticated as LLVMs, which has more infrastructure related to escaping and captured pointers, but should work in the majority of important cases. Any deficiency can be addressed in future patches. Related LLVM code: https://github.com/llvm/llvm-project/blob/27ade4b554774187d2c0afcf64cd16fa6d5f619d/llvm/lib/Transforms/Utils/InlineFunction.cpp#L1090 Differential Revision: https://reviews.llvm.org/D155712 --- mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td | 7 + mlir/lib/Dialect/LLVMIR/IR/LLVMInlining.cpp | 258 +++++++++++++++++++++ mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp | 39 ++++ .../test/Dialect/LLVMIR/inlining-alias-scopes.mlir | 199 ++++++++++++++++ mlir/test/Dialect/LLVMIR/inlining.mlir | 2 +- 5 files changed, 504 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td index 7b33ec8..c5d65f7 100644 --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td @@ -199,6 +199,13 @@ def AliasAnalysisOpInterface : OpInterface<"AliasAnalysisOpInterface"> { auto op = cast(this->getOperation()); op.setTbaaAttr(attr); }] + >, + InterfaceMethod< + /*desc=*/ "Returns a list of all pointer operands accessed by the " + "operation", + /*returnType=*/ "::llvm::SmallVector<::mlir::Value>", + /*methodName=*/ "getAccessedOperands", + /*args=*/ (ins) > ]; } diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMInlining.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMInlining.cpp index c2e319e..0426d5d 100644 --- a/mlir/lib/Dialect/LLVMIR/IR/LLVMInlining.cpp +++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMInlining.cpp @@ -16,6 +16,7 @@ #include "mlir/IR/Matchers.h" #include "mlir/Interfaces/DataLayoutInterfaces.h" #include "mlir/Transforms/InliningUtils.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "llvm-inliner" @@ -200,6 +201,238 @@ static ArrayAttr concatArrayAttr(ArrayAttr lhs, ArrayAttr rhs) { return ArrayAttr::get(lhs.getContext(), result); } +/// Attempts to return the underlying pointer value that `pointerValue` is based +/// on. This traverses down the chain of operations to the last operation +/// producing the base pointer and returns it. If it encounters an operation it +/// cannot further traverse through, returns the operation's result. +static Value getUnderlyingObject(Value pointerValue) { + while (true) { + if (auto gepOp = pointerValue.getDefiningOp()) { + pointerValue = gepOp.getBase(); + continue; + } + + if (auto addrCast = pointerValue.getDefiningOp()) { + pointerValue = addrCast.getOperand(); + continue; + } + + break; + } + + return pointerValue; +} + +/// Attempts to return the set of all underlying pointer values that +/// `pointerValue` is based on. This function traverses through select +/// operations and block arguments unlike getUnderlyingObject. +static SmallVector getUnderlyingObjectSet(Value pointerValue) { + SmallVector result; + + SmallVector workList{pointerValue}; + // Avoid dataflow loops. + SmallPtrSet seen; + do { + Value current = workList.pop_back_val(); + current = getUnderlyingObject(current); + + if (!seen.insert(current).second) + continue; + + if (auto selectOp = current.getDefiningOp()) { + workList.push_back(selectOp.getTrueValue()); + workList.push_back(selectOp.getFalseValue()); + continue; + } + + if (auto blockArg = dyn_cast(current)) { + Block *parentBlock = blockArg.getParentBlock(); + + // Attempt to find all block argument operands for every predecessor. + // If any operand to the block argument wasn't found in a predecessor, + // conservatively add the block argument to the result set. + SmallVector operands; + bool anyUnknown = false; + for (auto iter = parentBlock->pred_begin(); + iter != parentBlock->pred_end(); iter++) { + auto branch = dyn_cast((*iter)->getTerminator()); + if (!branch) { + result.push_back(blockArg); + anyUnknown = true; + break; + } + + Value operand = branch.getSuccessorOperands( + iter.getSuccessorIndex())[blockArg.getArgNumber()]; + if (!operand) { + result.push_back(blockArg); + anyUnknown = true; + break; + } + + operands.push_back(operand); + } + + if (!anyUnknown) + llvm::append_range(workList, operands); + + continue; + } + + result.push_back(current); + } while (!workList.empty()); + + return result; +} + +/// Creates a new AliasScopeAttr for every noalias parameter and attaches it to +/// the appropriate inlined memory operations in an attempt to preserve the +/// original semantics of the parameter attribute. +static void createNewAliasScopesFromNoAliasParameter( + Operation *call, iterator_range inlinedBlocks) { + + // First collect all noalias parameters. These have been specially marked by + // the `handleArgument` implementation by using the `ssa.copy` intrinsic and + // attaching a `noalias` attribute to it. + // These are only meant to be temporary and should therefore be deleted after + // we're done using them here. + SetVector noAliasParams; + for (Value argument : cast(call).getArgOperands()) { + for (Operation *user : argument.getUsers()) { + auto ssaCopy = llvm::dyn_cast(user); + if (!ssaCopy) + continue; + if (!ssaCopy->hasAttr(LLVM::LLVMDialect::getNoAliasAttrName())) + continue; + + noAliasParams.insert(ssaCopy); + } + } + + // If there were none, we have nothing to do here. + if (noAliasParams.empty()) + return; + + // Scope exit block to make it impossible to forget to get rid of the + // intrinsics. + auto exit = llvm::make_scope_exit([&] { + for (LLVM::SSACopyOp ssaCopyOp : noAliasParams) { + ssaCopyOp.replaceAllUsesWith(ssaCopyOp.getOperand()); + ssaCopyOp->erase(); + } + }); + + // Create a new domain for this specific inlining and a new scope for every + // noalias parameter. + auto functionDomain = LLVM::AliasScopeDomainAttr::get( + call->getContext(), cast(call).getCalleeAttr().getAttr()); + DenseMap pointerScopes; + for (LLVM::SSACopyOp copyOp : noAliasParams) { + auto scope = LLVM::AliasScopeAttr::get(functionDomain); + pointerScopes[copyOp] = scope; + + OpBuilder(call).create(call->getLoc(), scope); + } + + // Go through every instruction and attempt to find which noalias parameters + // it is definitely based on and definitely not based on. + for (Block &inlinedBlock : inlinedBlocks) { + for (auto aliasInterface : + inlinedBlock.getOps()) { + + // Collect the pointer arguments affected by the alias scopes. + SmallVector pointerArgs = aliasInterface.getAccessedOperands(); + + // Find the set of underlying pointers that this pointer is based on. + SmallPtrSet basedOnPointers; + for (Value pointer : pointerArgs) + llvm::copy(getUnderlyingObjectSet(pointer), + std::inserter(basedOnPointers, basedOnPointers.begin())); + + bool aliasesOtherKnownObject = false; + // Go through the based on pointers and check that they are either: + // * Constants that can be ignored (undef, poison, null pointer). + // * Based on a noalias parameter. + // * Other pointers that we know can't alias with our noalias parameter. + // + // Any other value might be a pointer based on any noalias parameter that + // hasn't been identified. In that case conservatively don't add any + // scopes to this operation indicating either aliasing or not aliasing + // with any parameter. + if (llvm::any_of(basedOnPointers, [&](Value object) { + if (matchPattern(object, m_Constant())) + return false; + + if (noAliasParams.contains(object.getDefiningOp())) + return false; + + // TODO: This should include other arguments from the inlined + // callable. + if (isa_and_nonnull( + object.getDefiningOp())) { + aliasesOtherKnownObject = true; + return false; + } + return true; + })) + continue; + + // Add all noalias parameter scopes to the noalias scope list that we are + // not based on. + SmallVector noAliasScopes; + for (LLVM::SSACopyOp noAlias : noAliasParams) { + if (basedOnPointers.contains(noAlias)) + continue; + + noAliasScopes.push_back(pointerScopes[noAlias]); + } + + if (!noAliasScopes.empty()) + aliasInterface.setNoAliasScopes( + concatArrayAttr(aliasInterface.getNoAliasScopesOrNull(), + ArrayAttr::get(call->getContext(), noAliasScopes))); + + // Don't add alias scopes to call operations or operations that might + // operate on pointers not based on any noalias parameter. + // Since we add all scopes to an operation's noalias list that it + // definitely doesn't alias, we mustn't do the same for the alias.scope + // list if other objects are involved. + // + // Consider the following case: + // %0 = llvm.alloca + // %1 = select %magic, %0, %noalias_param + // store 5, %1 (1) noalias=[scope(...)] + // ... + // store 3, %0 (2) noalias=[scope(noalias_param), scope(...)] + // + // We can add the scopes of any noalias parameters that aren't + // noalias_param's scope to (1) and add all of them to (2). We mustn't add + // the scope of noalias_param to the alias.scope list of (1) since + // that would mean (2) cannot alias with (1) which is wrong since both may + // store to %0. + // + // In conclusion, only add scopes to the alias.scope list if all pointers + // have a corresponding scope. + // Call operations are included in this list since we do not know whether + // the callee accesses any memory besides the ones passed as its + // arguments. + if (aliasesOtherKnownObject || + isa(aliasInterface.getOperation())) + continue; + + SmallVector aliasScopes; + for (LLVM::SSACopyOp noAlias : noAliasParams) + if (basedOnPointers.contains(noAlias)) + aliasScopes.push_back(pointerScopes[noAlias]); + + if (!aliasScopes.empty()) + aliasInterface.setAliasScopes( + concatArrayAttr(aliasInterface.getAliasScopesOrNull(), + ArrayAttr::get(call->getContext(), aliasScopes))); + } + } +} + /// Appends any alias scopes of the call operation to any inlined memory /// operation. static void @@ -235,6 +468,7 @@ appendCallOpAliasScopes(Operation *call, static void handleAliasScopes(Operation *call, iterator_range inlinedBlocks) { deepCloneAliasScopes(inlinedBlocks); + createNewAliasScopesFromNoAliasParameter(call, inlinedBlocks); appendCallOpAliasScopes(call, inlinedBlocks); } @@ -468,6 +702,7 @@ struct LLVMInlinerInterface : public DialectInlinerInterface { LLVM::LifetimeStartOp, LLVM::LoadOp, LLVM::MemcpyOp, + LLVM::MemcpyInlineOp, LLVM::MemmoveOp, LLVM::MemsetOp, LLVM::NoAliasScopeDeclOp, @@ -528,6 +763,29 @@ struct LLVMInlinerInterface : public DialectInlinerInterface { return handleByValArgument(builder, callable, argument, elementType, requestedAlignment); } + if (std::optional attr = + argumentAttrs.getNamed(LLVM::LLVMDialect::getNoAliasAttrName())) { + if (argument.use_empty()) + return argument; + + // This code is essentially a workaround for deficiencies in the + // inliner interface: We need to transform operations *after* inlined + // based on the argument attributes of the parameters *before* inlining. + // This method runs prior to actual inlining and thus cannot transform the + // post-inlining code, while `processInlinedCallBlocks` does not have + // access to pre-inlining function arguments. Additionally, it is required + // to distinguish which parameter an SSA value originally came from. + // As a workaround until this is changed: Create an ssa.copy intrinsic + // with the noalias attribute that can easily be found, and is extremely + // unlikely to exist in the code prior to inlining, using this to + // communicate between this method and `processInlinedCallBlocks`. + // TODO: Fix this by refactoring the inliner interface. + auto copyOp = builder.create(call->getLoc(), argument); + copyOp->setDiscardableAttr( + builder.getStringAttr(LLVM::LLVMDialect::getNoAliasAttrName()), + builder.getUnitAttr()); + return copyOp; + } return argument; } diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp index a613260..cff16af 100644 --- a/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp +++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp @@ -62,4 +62,43 @@ mlir::LLVM::detail::verifyAliasAnalysisOpInterface(Operation *op) { return isArrayOf(op, tags); } +SmallVector mlir::LLVM::AtomicCmpXchgOp::getAccessedOperands() { + return {getPtr()}; +} + +SmallVector mlir::LLVM::AtomicRMWOp::getAccessedOperands() { + return {getPtr()}; +} + +SmallVector mlir::LLVM::LoadOp::getAccessedOperands() { + return {getAddr()}; +} + +SmallVector mlir::LLVM::StoreOp::getAccessedOperands() { + return {getAddr()}; +} + +SmallVector mlir::LLVM::MemcpyOp::getAccessedOperands() { + return {getDst(), getSrc()}; +} + +SmallVector mlir::LLVM::MemcpyInlineOp::getAccessedOperands() { + return {getDst(), getSrc()}; +} + +SmallVector mlir::LLVM::MemmoveOp::getAccessedOperands() { + return {getDst(), getSrc()}; +} + +SmallVector mlir::LLVM::MemsetOp::getAccessedOperands() { + return {getDst()}; +} + +SmallVector mlir::LLVM::CallOp::getAccessedOperands() { + return llvm::to_vector( + llvm::make_filter_range(getArgOperands(), [](Value arg) { + return isa(arg.getType()); + })); +} + #include "mlir/Dialect/LLVMIR/LLVMInterfaces.cpp.inc" diff --git a/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir b/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir index dd40804..2945083 100644 --- a/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir +++ b/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir @@ -195,3 +195,202 @@ llvm.func @caller(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { llvm.call @callee_without_metadata(%arg1, %arg1, %arg0) {alias_scopes = [#alias_scope2]} : (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () llvm.return } + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @foo(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i64 + %1 = llvm.load %arg1 {alignment = 4 : i64} : !llvm.ptr -> f32 + %2 = llvm.getelementptr inbounds %arg0[%0] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %1, %2 {alignment = 4 : i64} : f32, !llvm.ptr + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.load +// CHECK-SAME: alias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK: llvm.store +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @foo(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @might_return_arg_derived(!llvm.ptr) -> !llvm.ptr + +llvm.func @foo(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i32 + %1 = llvm.call @might_return_arg_derived(%arg0) : (!llvm.ptr) -> !llvm.ptr + llvm.store %0, %1 : i32, !llvm.ptr + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.call +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK: llvm.store +// CHECK-NOT: alias_scopes +// CHECK-NOT: noalias_scopes +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @foo(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @random() -> i1 + +llvm.func @block_arg(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i32 + %1 = llvm.call @random() : () -> i1 + llvm.cond_br %1, ^bb0(%arg0 : !llvm.ptr), ^bb0(%arg1 : !llvm.ptr) + +^bb0(%arg2: !llvm.ptr): + llvm.store %0, %arg2 : i32, !llvm.ptr + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.call +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK: llvm.store +// CHECK: alias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @block_arg(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @random() -> i1 + +llvm.func @block_arg(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i32 + %1 = llvm.mlir.constant(1 : i64) : i64 + %2 = llvm.alloca %1 x i32 : (i64) -> !llvm.ptr + %3 = llvm.call @random() : () -> i1 + llvm.cond_br %3, ^bb0(%arg0 : !llvm.ptr), ^bb0(%2 : !llvm.ptr) + +^bb0(%arg2: !llvm.ptr): + llvm.store %0, %arg2 : i32, !llvm.ptr + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.call +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK: llvm.store +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @block_arg(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @unknown() -> !llvm.ptr +llvm.func @random() -> i1 + +llvm.func @unknown_object(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i32 + %1 = llvm.call @random() : () -> i1 + %2 = llvm.call @unknown() : () -> !llvm.ptr + llvm.cond_br %1, ^bb0(%arg0 : !llvm.ptr), ^bb0(%2 : !llvm.ptr) + +^bb0(%arg2: !llvm.ptr): + llvm.store %0, %arg2 : i32, !llvm.ptr + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.call +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK: llvm.call +// CHECK-NOT: alias_scopes +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK: llvm.store +// CHECK-NOT: alias_scopes +// CHECK-NOT: noalias_scopes +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @unknown_object(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} + +// ----- + +// CHECK-DAG: #[[DOMAIN:.*]] = #llvm.alias_scope_domain<{{.*}}> +// CHECK-DAG: #[[$ARG0_SCOPE:.*]] = #llvm.alias_scope +// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope + +llvm.func @supported_operations(%arg0: !llvm.ptr {llvm.noalias}, %arg1: !llvm.ptr {llvm.noalias}) { + %0 = llvm.mlir.constant(5 : i64) : i32 + llvm.store %0, %arg1 : i32, !llvm.ptr + %1 = llvm.load %arg1 : !llvm.ptr -> i32 + "llvm.intr.memcpy"(%arg0, %arg1, %1) <{ isVolatile = false }> : (!llvm.ptr, !llvm.ptr, i32) -> () + "llvm.intr.memmove"(%arg0, %arg1, %1) <{ isVolatile = false }> : (!llvm.ptr, !llvm.ptr, i32) -> () + "llvm.intr.memcpy.inline"(%arg0, %arg1) <{ isVolatile = false, len = 4 : i32}> : (!llvm.ptr, !llvm.ptr) -> () + %2 = llvm.trunc %0 : i32 to i8 + "llvm.intr.memset"(%arg0, %2, %1) <{ isVolatile = false}> : (!llvm.ptr, i8, i32) -> () + %3 = llvm.cmpxchg %arg0, %0, %1 seq_cst seq_cst : !llvm.ptr, i32 + %4 = llvm.atomicrmw add %arg0, %0 seq_cst : !llvm.ptr, i32 + llvm.return +} + +// CHECK-LABEL: llvm.func @bar +// CHECK: llvm.store +// CHECK-SAME: alias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK: llvm.load +// CHECK-SAME: alias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK: "llvm.intr.memcpy" +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK-NOT: noalias_scopes +// CHECK: "llvm.intr.memmove" +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK-NOT: noalias_scopes +// CHECK: "llvm.intr.memcpy.inline" +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]], #[[$ARG1_SCOPE]]] +// CHECK-NOT: noalias_scopes +// CHECK: "llvm.intr.memset" +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK: llvm.cmpxchg +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +// CHECK: llvm.atomicrmw +// CHECK-SAME: alias_scopes = [#[[$ARG0_SCOPE]]] +// CHECK-SAME: noalias_scopes = [#[[$ARG1_SCOPE]]] +llvm.func @bar(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: !llvm.ptr) { + llvm.call @supported_operations(%arg0, %arg2) : (!llvm.ptr, !llvm.ptr) -> () + llvm.return +} diff --git a/mlir/test/Dialect/LLVMIR/inlining.mlir b/mlir/test/Dialect/LLVMIR/inlining.mlir index b22bfb4..b59f141 100644 --- a/mlir/test/Dialect/LLVMIR/inlining.mlir +++ b/mlir/test/Dialect/LLVMIR/inlining.mlir @@ -524,7 +524,7 @@ llvm.func @test_byval_global() { // ----- -llvm.func @ignored_attrs(%ptr : !llvm.ptr { llvm.inreg, llvm.noalias, llvm.nocapture, llvm.nofree, llvm.preallocated = i32, llvm.returned, llvm.alignstack = 32 : i64, llvm.writeonly, llvm.noundef, llvm.nonnull }, %x : i32 { llvm.zeroext }) -> (!llvm.ptr { llvm.noundef, llvm.inreg, llvm.nonnull }) { +llvm.func @ignored_attrs(%ptr : !llvm.ptr { llvm.inreg, llvm.nocapture, llvm.nofree, llvm.preallocated = i32, llvm.returned, llvm.alignstack = 32 : i64, llvm.writeonly, llvm.noundef, llvm.nonnull }, %x : i32 { llvm.zeroext }) -> (!llvm.ptr { llvm.noundef, llvm.inreg, llvm.nonnull }) { llvm.return %ptr : !llvm.ptr } -- 2.7.4