diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td @@ -199,6 +199,13 @@ 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 --- 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 @@ 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 @@ static void handleAliasScopes(Operation *call, iterator_range inlinedBlocks) { deepCloneAliasScopes(inlinedBlocks); + createNewAliasScopesFromNoAliasParameter(call, inlinedBlocks); appendCallOpAliasScopes(call, inlinedBlocks); } @@ -468,6 +702,7 @@ LLVM::LifetimeStartOp, LLVM::LoadOp, LLVM::MemcpyOp, + LLVM::MemcpyInlineOp, LLVM::MemmoveOp, LLVM::MemsetOp, LLVM::NoAliasScopeDeclOp, @@ -528,6 +763,29 @@ 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 --- a/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp +++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMInterfaces.cpp @@ -62,4 +62,43 @@ 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 --- a/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir +++ b/mlir/test/Dialect/LLVMIR/inlining-alias-scopes.mlir @@ -195,3 +195,202 @@ 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 --- a/mlir/test/Dialect/LLVMIR/inlining.mlir +++ b/mlir/test/Dialect/LLVMIR/inlining.mlir @@ -524,7 +524,7 @@ // ----- -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 }