31#include "llvm/IR/IntrinsicsAMDGPU.h"
36#define DEBUG_TYPE "amdgpu-atomic-optimizer"
43struct ReplacementInfo {
66class AMDGPUAtomicOptimizerImpl
79 Value *
const Identity)
const;
81 Value *
const Identity)
const;
84 std::pair<Value *, Value *>
90 bool ValDivergent)
const;
93 AMDGPUAtomicOptimizerImpl() =
delete;
98 :
F(
F), UA(UA),
DL(
F.getDataLayout()), DTU(DTU), ST(ST),
100 ScanImpl(ScanImpl) {}
110char AMDGPUAtomicOptimizer::ID = 0;
114bool AMDGPUAtomicOptimizer::runOnFunction(
Function &
F) {
115 if (skipFunction(
F)) {
120 getAnalysis<UniformityInfoWrapperPass>().getUniformityInfo();
123 getAnalysisIfAvailable<DominatorTreeWrapperPass>();
125 DomTreeUpdater::UpdateStrategy::Lazy);
131 return AMDGPUAtomicOptimizerImpl(
F, UA, DTU, ST, ScanImpl).run();
139 DomTreeUpdater::UpdateStrategy::Lazy);
142 bool IsChanged = AMDGPUAtomicOptimizerImpl(
F, UA, DTU, ST, ScanImpl).run();
153bool AMDGPUAtomicOptimizerImpl::run() {
162 const bool Changed = !ToReplace.empty();
164 for (ReplacementInfo &Info : ToReplace) {
187void AMDGPUAtomicOptimizerImpl::visitAtomicRMWInst(
AtomicRMWInst &
I) {
189 switch (
I.getPointerAddressSpace()) {
220 !(
I.getType()->isFloatTy() ||
I.getType()->isDoubleTy())) {
224 const unsigned PtrIdx = 0;
225 const unsigned ValIdx = 1;
229 if (UA.isDivergentUse(
I.getOperandUse(PtrIdx))) {
233 bool ValDivergent = UA.isDivergentUse(
I.getOperandUse(ValIdx));
250 const ReplacementInfo
Info = {&
I,
Op, ValIdx, ValDivergent};
252 ToReplace.push_back(Info);
255void AMDGPUAtomicOptimizerImpl::visitIntrinsicInst(
IntrinsicInst &
I) {
258 switch (
I.getIntrinsicID()) {
261 case Intrinsic::amdgcn_struct_buffer_atomic_add:
262 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_add:
263 case Intrinsic::amdgcn_raw_buffer_atomic_add:
264 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_add:
267 case Intrinsic::amdgcn_struct_buffer_atomic_sub:
268 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_sub:
269 case Intrinsic::amdgcn_raw_buffer_atomic_sub:
270 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_sub:
273 case Intrinsic::amdgcn_struct_buffer_atomic_and:
274 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_and:
275 case Intrinsic::amdgcn_raw_buffer_atomic_and:
276 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_and:
279 case Intrinsic::amdgcn_struct_buffer_atomic_or:
280 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_or:
281 case Intrinsic::amdgcn_raw_buffer_atomic_or:
282 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_or:
285 case Intrinsic::amdgcn_struct_buffer_atomic_xor:
286 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_xor:
287 case Intrinsic::amdgcn_raw_buffer_atomic_xor:
288 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_xor:
291 case Intrinsic::amdgcn_struct_buffer_atomic_smin:
292 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_smin:
293 case Intrinsic::amdgcn_raw_buffer_atomic_smin:
294 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_smin:
297 case Intrinsic::amdgcn_struct_buffer_atomic_umin:
298 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_umin:
299 case Intrinsic::amdgcn_raw_buffer_atomic_umin:
300 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_umin:
303 case Intrinsic::amdgcn_struct_buffer_atomic_smax:
304 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_smax:
305 case Intrinsic::amdgcn_raw_buffer_atomic_smax:
306 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_smax:
309 case Intrinsic::amdgcn_struct_buffer_atomic_umax:
310 case Intrinsic::amdgcn_struct_ptr_buffer_atomic_umax:
311 case Intrinsic::amdgcn_raw_buffer_atomic_umax:
312 case Intrinsic::amdgcn_raw_ptr_buffer_atomic_umax:
317 const unsigned ValIdx = 0;
319 const bool ValDivergent = UA.isDivergentUse(
I.getOperandUse(ValIdx));
335 for (
unsigned Idx = 1;
Idx <
I.getNumOperands();
Idx++) {
336 if (UA.isDivergentUse(
I.getOperandUse(
Idx))) {
344 const ReplacementInfo
Info = {&
I,
Op, ValIdx, ValDivergent};
346 ToReplace.push_back(Info);
359 return B.CreateBinOp(Instruction::Add,
LHS,
RHS);
363 return B.CreateBinOp(Instruction::Sub,
LHS,
RHS);
367 return B.CreateBinOp(Instruction::And,
LHS,
RHS);
369 return B.CreateBinOp(Instruction::Or,
LHS,
RHS);
371 return B.CreateBinOp(Instruction::Xor,
LHS,
RHS);
386 return B.CreateMaxNum(
LHS,
RHS);
388 return B.CreateMinNum(
LHS,
RHS);
399 Value *
const Identity)
const {
400 Type *AtomicTy =
V->getType();
401 Module *
M =
B.GetInsertBlock()->getModule();
407 B.CreateIntrinsic(Intrinsic::amdgcn_update_dpp, AtomicTy,
408 {Identity, V, B.getInt32(DPP::ROW_XMASK0 | 1 << Idx),
409 B.getInt32(0xf), B.getInt32(0xf), B.getFalse()}));
414 Value *Permlanex16Call =
415 B.CreateIntrinsic(AtomicTy, Intrinsic::amdgcn_permlanex16,
417 B.getInt32(0),
B.getFalse(),
B.getFalse()});
423 if (
ST.hasPermLane64()) {
425 Value *Permlane64Call =
426 B.CreateIntrinsic(AtomicTy, Intrinsic::amdgcn_permlane64, V);
433 M, Intrinsic::amdgcn_readlane, AtomicTy);
434 Value *Lane0 =
B.CreateCall(ReadLane, {
V,
B.getInt32(0)});
435 Value *Lane32 =
B.CreateCall(ReadLane, {
V,
B.getInt32(32)});
443 Value *Identity)
const {
444 Type *AtomicTy =
V->getType();
445 Module *
M =
B.GetInsertBlock()->getModule();
447 M, Intrinsic::amdgcn_update_dpp, AtomicTy);
452 B.CreateCall(UpdateDPP,
453 {Identity, V, B.getInt32(DPP::ROW_SHR0 | 1 << Idx),
454 B.getInt32(0xf), B.getInt32(0xf), B.getFalse()}));
456 if (
ST.hasDPPBroadcasts()) {
460 B.CreateCall(UpdateDPP,
461 {Identity, V, B.getInt32(DPP::BCAST15), B.getInt32(0xa),
462 B.getInt32(0xf), B.getFalse()}));
465 B.CreateCall(UpdateDPP,
466 {Identity, V, B.getInt32(DPP::BCAST31), B.getInt32(0xc),
467 B.getInt32(0xf), B.getFalse()}));
476 B.CreateIntrinsic(AtomicTy, Intrinsic::amdgcn_permlanex16,
478 B.getInt32(-1),
B.getFalse(),
B.getFalse()});
480 Value *UpdateDPPCall =
B.CreateCall(
482 B.getInt32(0xa),
B.getInt32(0xf),
B.getFalse()});
485 if (!
ST.isWave32()) {
487 Value *
const Lane31 =
B.CreateIntrinsic(
488 AtomicTy, Intrinsic::amdgcn_readlane, {
V,
B.getInt32(31)});
490 Value *UpdateDPPCall =
B.CreateCall(
492 B.getInt32(0xc),
B.getInt32(0xf),
B.getFalse()});
503 Value *Identity)
const {
504 Type *AtomicTy =
V->getType();
505 Module *
M =
B.GetInsertBlock()->getModule();
507 M, Intrinsic::amdgcn_update_dpp, AtomicTy);
508 if (
ST.hasDPPWavefrontShifts()) {
510 V =
B.CreateCall(UpdateDPP,
512 B.getInt32(0xf),
B.getFalse()});
515 M, Intrinsic::amdgcn_readlane, AtomicTy);
517 M, Intrinsic::amdgcn_writelane, AtomicTy);
522 V =
B.CreateCall(UpdateDPP,
524 B.getInt32(0xf),
B.getInt32(0xf),
B.getFalse()});
527 V =
B.CreateCall(WriteLane, {
B.CreateCall(ReadLane, {Old,
B.getInt32(15)}),
530 if (!
ST.isWave32()) {
534 {
B.CreateCall(ReadLane, {Old,
B.getInt32(31)}),
B.getInt32(32), V});
539 {
B.CreateCall(ReadLane, {Old,
B.getInt32(47)}),
B.getInt32(48), V});
551std::pair<Value *, Value *> AMDGPUAtomicOptimizerImpl::buildScanIteratively(
554 auto *Ty =
I.getType();
555 auto *WaveTy =
B.getIntNTy(
ST.getWavefrontSize());
556 auto *EntryBB =
I.getParent();
557 auto NeedResult = !
I.use_empty();
560 B.CreateIntrinsic(Intrinsic::amdgcn_ballot, WaveTy,
B.getTrue());
563 B.SetInsertPoint(ComputeLoop);
567 PHINode *OldValuePhi =
nullptr;
569 OldValuePhi =
B.CreatePHI(Ty, 2,
"OldValuePhi");
572 auto *ActiveBits =
B.CreatePHI(WaveTy, 2,
"ActiveBits");
573 ActiveBits->addIncoming(Ballot, EntryBB);
577 B.CreateIntrinsic(Intrinsic::cttz, WaveTy, {ActiveBits,
B.getTrue()});
579 auto *LaneIdxInt =
B.CreateTrunc(FF1,
B.getInt32Ty());
582 Value *LaneValue =
B.CreateIntrinsic(
V->getType(), Intrinsic::amdgcn_readlane,
587 Value *OldValue =
nullptr;
589 OldValue =
B.CreateIntrinsic(
V->getType(), Intrinsic::amdgcn_writelane,
590 {Accumulator, LaneIdxInt, OldValuePhi});
596 Accumulator->addIncoming(NewAccumulator, ComputeLoop);
600 auto *
Mask =
B.CreateShl(ConstantInt::get(WaveTy, 1), FF1);
602 auto *InverseMask =
B.CreateXor(Mask, ConstantInt::get(WaveTy, -1));
603 auto *NewActiveBits =
B.CreateAnd(ActiveBits, InverseMask);
604 ActiveBits->addIncoming(NewActiveBits, ComputeLoop);
607 auto *IsEnd =
B.CreateICmpEQ(NewActiveBits, ConstantInt::get(WaveTy, 0));
608 B.CreateCondBr(IsEnd, ComputeEnd, ComputeLoop);
610 B.SetInsertPoint(ComputeEnd);
612 return {OldValue, NewAccumulator};
654void AMDGPUAtomicOptimizerImpl::optimizeAtomic(
Instruction &
I,
657 bool ValDivergent)
const {
662 B.setIsFPConstrained(
I.getFunction()->hasFnAttribute(Attribute::StrictFP));
677 PixelEntryBB =
I.getParent();
679 Value *
const Cond =
B.CreateIntrinsic(Intrinsic::amdgcn_ps_live, {}, {});
684 PixelExitBB =
I.getParent();
686 I.moveBefore(NonHelperTerminator);
687 B.SetInsertPoint(&
I);
690 Type *
const Ty =
I.getType();
691 Type *Int32Ty =
B.getInt32Ty();
693 [[maybe_unused]]
const unsigned TyBitWidth =
DL.getTypeSizeInBits(Ty);
697 Value *
V =
I.getOperand(ValIdx);
701 Type *
const WaveTy =
B.getIntNTy(
ST.getWavefrontSize());
703 B.CreateIntrinsic(Intrinsic::amdgcn_ballot, WaveTy,
B.getTrue());
711 Mbcnt =
B.CreateIntrinsic(Intrinsic::amdgcn_mbcnt_lo, {},
712 {Ballot,
B.getInt32(0)});
714 Value *
const ExtractLo =
B.CreateTrunc(Ballot, Int32Ty);
715 Value *
const ExtractHi =
B.CreateTrunc(
B.CreateLShr(Ballot, 32), Int32Ty);
716 Mbcnt =
B.CreateIntrinsic(Intrinsic::amdgcn_mbcnt_lo, {},
717 {ExtractLo,
B.getInt32(0)});
719 B.CreateIntrinsic(Intrinsic::amdgcn_mbcnt_hi, {}, {ExtractHi, Mbcnt});
735 Value *ExclScan =
nullptr;
736 Value *NewV =
nullptr;
738 const bool NeedResult = !
I.use_empty();
749 B.CreateIntrinsic(Intrinsic::amdgcn_set_inactive, Ty, {
V, Identity});
750 if (!NeedResult &&
ST.hasPermLaneX16()) {
754 NewV = buildReduction(
B, ScanOp, NewV, Identity);
756 NewV = buildScan(
B, ScanOp, NewV, Identity);
758 ExclScan = buildShiftRight(
B, NewV, Identity);
762 Value *
const LastLaneIdx =
B.getInt32(
ST.getWavefrontSize() - 1);
763 NewV =
B.CreateIntrinsic(Ty, Intrinsic::amdgcn_readlane,
764 {NewV, LastLaneIdx});
767 NewV =
B.CreateIntrinsic(Intrinsic::amdgcn_strict_wwm, Ty, NewV);
772 std::tie(ExclScan, NewV) = buildScanIteratively(
B, ScanOp, Identity, V,
I,
773 ComputeLoop, ComputeEnd);
786 Value *
const Ctpop =
B.CreateIntCast(
787 B.CreateUnaryIntrinsic(Intrinsic::ctpop, Ballot), Ty,
false);
793 Value *
const Ctpop =
B.CreateIntCast(
794 B.CreateUnaryIntrinsic(Intrinsic::ctpop, Ballot), Int32Ty,
false);
795 Value *
const CtpopFP =
B.CreateUIToFP(Ctpop, Ty);
796 NewV =
B.CreateFMul(V, CtpopFP);
815 Value *
const Ctpop =
B.CreateIntCast(
816 B.CreateUnaryIntrinsic(Intrinsic::ctpop, Ballot), Ty,
false);
825 Value *
const Cond =
B.CreateICmpEQ(Mbcnt,
B.getInt32(0));
853 B.SetInsertPoint(ComputeEnd);
855 B.Insert(Terminator);
859 B.SetInsertPoint(OriginalBB);
860 B.CreateBr(ComputeLoop);
874 DTU.applyUpdates(DomTreeUpdates);
876 Predecessor = ComputeEnd;
878 Predecessor = OriginalBB;
881 B.SetInsertPoint(SingleLaneTerminator);
891 B.SetInsertPoint(&
I);
897 PHI->addIncoming(NewI, SingleLaneTerminator->
getParent());
901 Value *BroadcastI =
nullptr;
902 BroadcastI =
B.CreateIntrinsic(Ty, Intrinsic::amdgcn_readfirstlane,
PHI);
908 Value *LaneOffset =
nullptr;
912 B.CreateIntrinsic(Intrinsic::amdgcn_strict_wwm, Ty, ExclScan);
914 LaneOffset = ExclScan;
919 Mbcnt = isAtomicFloatingPointTy ?
B.CreateUIToFP(Mbcnt, Ty)
920 :
B.CreateIntCast(Mbcnt, Ty,
false);
936 LaneOffset =
B.CreateSelect(
Cond, Identity, V);
939 LaneOffset =
buildMul(
B, V,
B.CreateAnd(Mbcnt, 1));
943 LaneOffset =
B.CreateFMul(V, Mbcnt);
949 if (isAtomicFloatingPointTy) {
969 PHI->addIncoming(Result,
I.getParent());
970 I.replaceAllUsesWith(
PHI);
973 I.replaceAllUsesWith(Result);
982 "AMDGPU atomic optimizations",
false,
false)
989 return new AMDGPUAtomicOptimizer(ScanStrategy);
static Constant * getIdentityValueForAtomicOp(Type *const Ty, AtomicRMWInst::BinOp Op)
static bool isLegalCrossLaneType(Type *Ty)
static Value * buildMul(IRBuilder<> &B, Value *LHS, Value *RHS)
static Value * buildNonAtomicBinOp(IRBuilder<> &B, AtomicRMWInst::BinOp Op, Value *LHS, Value *RHS)
MachineBasicBlock MachineBasicBlock::iterator DebugLoc DL
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
Analysis containing CSE Info
Returns the sub type a function will return at a given Idx Should correspond to the result type of an ExtractValue instruction executed with just that one unsigned Idx
AMD GCN specific subclass of TargetSubtarget.
Generic memory optimizations
#define INITIALIZE_PASS_DEPENDENCY(depName)
#define INITIALIZE_PASS_END(passName, arg, name, cfg, analysis)
#define INITIALIZE_PASS_BEGIN(passName, arg, name, cfg, analysis)
const SmallVectorImpl< MachineOperand > & Cond
assert(ImpDefSCC.getReg()==AMDGPU::SCC &&ImpDefSCC.isDef())
void visit(MachineFunction &MF, MachineBasicBlock &Start, std::function< void(MachineBasicBlock *)> op)
Target-Independent Code Generator Pass Configuration Options pass.
static APFloat getNaN(const fltSemantics &Sem, bool Negative=false, uint64_t payload=0)
Factory for NaN values.
static APFloat getZero(const fltSemantics &Sem, bool Negative=false)
Factory for Positive and Negative Zero.
static APInt getMaxValue(unsigned numBits)
Gets maximum unsigned value of APInt for specific bit width.
static APInt getSignedMaxValue(unsigned numBits)
Gets maximum signed value of APInt for a specific bit width.
static APInt getMinValue(unsigned numBits)
Gets minimum unsigned value of APInt for a specific bit width.
static APInt getSignedMinValue(unsigned numBits)
Gets minimum signed value of APInt for a specific bit width.
A container for analyses that lazily runs them and caches their results.
PassT::Result & getResult(IRUnitT &IR, ExtraArgTs... ExtraArgs)
Get the result of an analysis pass for a given IR unit.
Represent the analysis usage information of a pass.
AnalysisUsage & addRequired()
AnalysisUsage & addPreserved()
Add the specified Pass class to the set of analyses preserved by this pass.
an instruction that atomically reads a memory location, combines it with another value,...
static bool isFPOperation(BinOp Op)
BinOp
This enumeration lists the possible modifications atomicrmw can make.
@ Min
*p = old <signed v ? old : v
@ Max
*p = old >signed v ? old : v
@ UMin
*p = old <unsigned v ? old : v
@ FMin
*p = minnum(old, v) minnum matches the behavior of llvm.minnum.
@ UMax
*p = old >unsigned v ? old : v
@ FMax
*p = maxnum(old, v) maxnum matches the behavior of llvm.maxnum.
LLVM Basic Block Representation.
InstListType::const_iterator getFirstNonPHIIt() const
Iterator returning form of getFirstNonPHI.
static BasicBlock * Create(LLVMContext &Context, const Twine &Name="", Function *Parent=nullptr, BasicBlock *InsertBefore=nullptr)
Creates a new BasicBlock.
const Instruction * getTerminator() const LLVM_READONLY
Returns the terminator instruction if the block is well formed or null if the block is not well forme...
Conditional or Unconditional Branch instruction.
This class represents a function call, abstracting a target machine's calling convention.
Predicate
This enumeration lists the possible predicates for CmpInst subclasses.
@ ICMP_SLT
signed less than
@ ICMP_UGT
unsigned greater than
@ ICMP_SGT
signed greater than
@ ICMP_ULT
unsigned less than
This is the shared class of boolean and integer constants.
bool isOne() const
This is just a convenience method to make client code smaller for a common case.
This is an important base class in LLVM.
This class represents an Operation in the Expression.
A parsed version of the target data layout string in and methods for querying it.
Analysis pass which computes a DominatorTree.
static constexpr UpdateKind Delete
static constexpr UpdateKind Insert
Legacy analysis pass which computes a DominatorTree.
DominatorTree & getDomTree()
FunctionPass class - This class is used to implement most global optimizations.
virtual bool runOnFunction(Function &F)=0
runOnFunction - Virtual method overriden by subclasses to do the per-function processing of the pass.
This provides a uniform API for creating instructions and inserting them into a basic block: either a...
Base class for instruction visitors.
RetTy visitIntrinsicInst(IntrinsicInst &I)
RetTy visitAtomicRMWInst(AtomicRMWInst &I)
A wrapper class for inspecting calls to intrinsic functions.
This is an important class for using LLVM in a threaded context.
A Module instance is used to store all the information related to an LLVM module.
void addIncoming(Value *V, BasicBlock *BB)
Add an incoming value to the end of the PHI list.
virtual void getAnalysisUsage(AnalysisUsage &) const
getAnalysisUsage - This function should be overriden by passes that need analysis information to do t...
static PoisonValue * get(Type *T)
Static factory methods - Return an 'poison' object of the specified type.
A set of analyses that are preserved following a run of a transformation pass.
static PreservedAnalyses all()
Construct a special preserved set that preserves all passes.
void preserve()
Mark an analysis as preserved.
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
Primary interface to the complete machine description for the target machine.
const STC & getSubtarget(const Function &F) const
This method returns a pointer to the specified type of TargetSubtargetInfo.
Target-Independent Code Generator Pass Configuration Options.
TMC & getTM() const
Get the right type of TargetMachine for this target.
The instances of the Type class are immutable: once they are created, they are never changed.
unsigned getIntegerBitWidth() const
const fltSemantics & getFltSemantics() const
@ FloatTyID
32-bit floating point type
@ IntegerTyID
Arbitrary bit width integers.
@ DoubleTyID
64-bit floating point type
LLVMContext & getContext() const
Return the LLVMContext in which this type was uniqued.
bool isFloatingPointTy() const
Return true if this is one of the floating-point types.
TypeID getTypeID() const
Return the type id for the type.
TypeSize getPrimitiveSizeInBits() const LLVM_READONLY
Return the basic size of this type if it is a primitive type.
void setOperand(unsigned i, Value *Val)
LLVM Value Representation.
const ParentTy * getParent() const
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
@ LOCAL_ADDRESS
Address space for local memory.
@ GLOBAL_ADDRESS
Address space for global memory (RAT0, VTX0).
constexpr std::underlying_type_t< E > Mask()
Get a bitmask with 1s in all places up to the high-order bit of E's largest value.
@ AMDGPU_PS
Used for Mesa/AMDPAL pixel shaders.
@ C
The default llvm calling convention, compatible with C.
unsigned ID
LLVM IR allows to use arbitrary numbers as calling convention identifiers.
Function * getOrInsertDeclaration(Module *M, ID id, ArrayRef< Type * > Tys={})
Look up the Function declaration of the intrinsic id in the Module M.
This is an optimization pass for GlobalISel generic memory operations.
FunctionPass * createAMDGPUAtomicOptimizerPass(ScanOptions ScanStrategy)
DWARFExpression::Operation Op
constexpr unsigned BitWidth
char & AMDGPUAtomicOptimizerID
Instruction * SplitBlockAndInsertIfThen(Value *Cond, BasicBlock::iterator SplitBefore, bool Unreachable, MDNode *BranchWeights=nullptr, DomTreeUpdater *DTU=nullptr, LoopInfo *LI=nullptr, BasicBlock *ThenBlock=nullptr)
Split the containing block at the specified instruction - everything before SplitBefore stays in the ...
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM)