[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: 27ade4b554/llvm/lib/Transforms/Utils/InlineFunction.cpp (L1090)

Differential Revision: https://reviews.llvm.org/D155712
This commit is contained in:
Markus Böck
2023-07-19 16:35:54 +02:00
parent e1aa4e7b38
commit f9173c2958
5 changed files with 504 additions and 1 deletions

View File

@@ -199,6 +199,13 @@ def AliasAnalysisOpInterface : OpInterface<"AliasAnalysisOpInterface"> {
auto op = cast<ConcreteOp>(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)
>
];
}

View File

@@ -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<LLVM::GEPOp>()) {
pointerValue = gepOp.getBase();
continue;
}
if (auto addrCast = pointerValue.getDefiningOp<LLVM::AddrSpaceCastOp>()) {
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<Value> getUnderlyingObjectSet(Value pointerValue) {
SmallVector<Value> result;
SmallVector<Value> workList{pointerValue};
// Avoid dataflow loops.
SmallPtrSet<Value, 4> seen;
do {
Value current = workList.pop_back_val();
current = getUnderlyingObject(current);
if (!seen.insert(current).second)
continue;
if (auto selectOp = current.getDefiningOp<LLVM::SelectOp>()) {
workList.push_back(selectOp.getTrueValue());
workList.push_back(selectOp.getFalseValue());
continue;
}
if (auto blockArg = dyn_cast<BlockArgument>(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<Value> operands;
bool anyUnknown = false;
for (auto iter = parentBlock->pred_begin();
iter != parentBlock->pred_end(); iter++) {
auto branch = dyn_cast<BranchOpInterface>((*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<Region::iterator> 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<LLVM::SSACopyOp> noAliasParams;
for (Value argument : cast<LLVM::CallOp>(call).getArgOperands()) {
for (Operation *user : argument.getUsers()) {
auto ssaCopy = llvm::dyn_cast<LLVM::SSACopyOp>(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<LLVM::CallOp>(call).getCalleeAttr().getAttr());
DenseMap<Value, LLVM::AliasScopeAttr> pointerScopes;
for (LLVM::SSACopyOp copyOp : noAliasParams) {
auto scope = LLVM::AliasScopeAttr::get(functionDomain);
pointerScopes[copyOp] = scope;
OpBuilder(call).create<LLVM::NoAliasScopeDeclOp>(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<LLVM::AliasAnalysisOpInterface>()) {
// Collect the pointer arguments affected by the alias scopes.
SmallVector<Value> pointerArgs = aliasInterface.getAccessedOperands();
// Find the set of underlying pointers that this pointer is based on.
SmallPtrSet<Value, 4> 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<LLVM::SSACopyOp>()))
return false;
// TODO: This should include other arguments from the inlined
// callable.
if (isa_and_nonnull<LLVM::AllocaOp, LLVM::AddressOfOp>(
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<Attribute> 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<LLVM::CallOp>(aliasInterface.getOperation()))
continue;
SmallVector<Attribute> 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<Region::iterator> 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<NamedAttribute> 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<LLVM::SSACopyOp>(call->getLoc(), argument);
copyOp->setDiscardableAttr(
builder.getStringAttr(LLVM::LLVMDialect::getNoAliasAttrName()),
builder.getUnitAttr());
return copyOp;
}
return argument;
}

View File

@@ -62,4 +62,43 @@ mlir::LLVM::detail::verifyAliasAnalysisOpInterface(Operation *op) {
return isArrayOf<TBAATagAttr>(op, tags);
}
SmallVector<Value> mlir::LLVM::AtomicCmpXchgOp::getAccessedOperands() {
return {getPtr()};
}
SmallVector<Value> mlir::LLVM::AtomicRMWOp::getAccessedOperands() {
return {getPtr()};
}
SmallVector<Value> mlir::LLVM::LoadOp::getAccessedOperands() {
return {getAddr()};
}
SmallVector<Value> mlir::LLVM::StoreOp::getAccessedOperands() {
return {getAddr()};
}
SmallVector<Value> mlir::LLVM::MemcpyOp::getAccessedOperands() {
return {getDst(), getSrc()};
}
SmallVector<Value> mlir::LLVM::MemcpyInlineOp::getAccessedOperands() {
return {getDst(), getSrc()};
}
SmallVector<Value> mlir::LLVM::MemmoveOp::getAccessedOperands() {
return {getDst(), getSrc()};
}
SmallVector<Value> mlir::LLVM::MemsetOp::getAccessedOperands() {
return {getDst()};
}
SmallVector<Value> mlir::LLVM::CallOp::getAccessedOperands() {
return llvm::to_vector(
llvm::make_filter_range(getArgOperands(), [](Value arg) {
return isa<LLVMPointerType>(arg.getType());
}));
}
#include "mlir/Dialect/LLVMIR/LLVMInterfaces.cpp.inc"

View File

@@ -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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
// CHECK-DAG: #[[$ARG1_SCOPE:.*]] = #llvm.alias_scope<id = {{.*}}, domain = #[[DOMAIN]]{{(,.*)?}}>
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
}

View File

@@ -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
}