83#define DEBUG_TYPE "loop-flatten"
85STATISTIC(NumFlattened,
"Number of loops flattened");
97 const ScalarOptions &Opts;
98 Loop *OuterLoop =
nullptr;
99 Loop *InnerLoop =
nullptr;
101 PHINode *InnerInductionPHI =
nullptr;
102 PHINode *OuterInductionPHI =
nullptr;
106 Value *InnerTripCount =
nullptr;
107 Value *OuterTripCount =
nullptr;
124 bool Widened =
false;
127 PHINode *NarrowInnerInductionPHI =
nullptr;
128 PHINode *NarrowOuterInductionPHI =
nullptr;
132 Value *NewTripCount =
nullptr;
135 : Opts(ScalarOptions::
Global), OuterLoop(OL), InnerLoop(IL) {}
137 bool isNarrowInductionPhi(PHINode *Phi) {
141 return NarrowInnerInductionPHI ==
Phi || NarrowOuterInductionPHI ==
Phi;
143 bool isInnerLoopIncrement(User *U) {
144 return InnerIncrement ==
U;
146 bool isOuterLoopIncrement(User *U) {
147 return OuterIncrement ==
U;
149 bool isInnerLoopTest(User *U) {
150 return InnerBranch->getCondition() ==
U;
153 bool checkOuterInductionPhiUsers(SmallPtrSet<Value *, 4> &ValidOuterPHIUses) {
154 for (User *U : OuterInductionPHI->users()) {
155 if (isOuterLoopIncrement(U))
158 auto IsValidOuterPHIUses = [&] (
User *
U) ->
bool {
159 LLVM_DEBUG(
dbgs() <<
"Found use of outer induction variable: ";
U->dump());
160 if (!ValidOuterPHIUses.
count(U)) {
161 LLVM_DEBUG(
dbgs() <<
"Did not match expected pattern, bailing\n");
169 for (
auto *K :
V->users()) {
170 if (!IsValidOuterPHIUses(K))
176 if (!IsValidOuterPHIUses(U))
182 bool matchLinearIVUser(User *U,
Value *InnerTripCount,
183 SmallPtrSet<Value *, 4> &ValidOuterPHIUses) {
184 LLVM_DEBUG(
dbgs() <<
"Checking linear i*M+j expression for: ";
U->dump());
185 Value *MatchedMul =
nullptr;
186 Value *MatchedItCount =
nullptr;
216 return !isInstructionTriviallyDead(cast<Instruction>(U));
224 if (Widened && (IsAdd || IsGEP) &&
226 assert(MatchedItCount->
getType() == InnerInductionPHI->getType() &&
227 "Unexpected type mismatch in types after widening");
234 InnerTripCount->dump());
236 if ((IsAdd || IsAddTrunc || IsGEP) && MatchedItCount == InnerTripCount) {
238 ValidOuterPHIUses.
insert(MatchedMul);
239 LinearIVUses.insert(U);
243 LLVM_DEBUG(
dbgs() <<
"Did not match expected pattern, bailing\n");
247 bool checkInnerInductionPhiUsers(SmallPtrSet<Value *, 4> &ValidOuterPHIUses) {
248 Value *SExtInnerTripCount = InnerTripCount;
253 for (User *U : InnerInductionPHI->users()) {
255 if (isInnerLoopIncrement(U)) {
256 LLVM_DEBUG(
dbgs() <<
"Use is inner loop increment, continuing\n");
265 U = *
U->user_begin();
272 if (isInnerLoopTest(U)) {
277 if (!matchLinearIVUser(U, SExtInnerTripCount, ValidOuterPHIUses)) {
311 LLVM_DEBUG(
dbgs() <<
"Backedge-taken count is not predictable\n");
318 const SCEV *SCEVTripCount =
320 BackedgeTakenCount->
getType(), L);
323 if (SCEVRHS == SCEVTripCount)
327 const SCEV *BackedgeTCExt =
nullptr;
329 const SCEV *SCEVTripCountExt;
335 if (SCEVRHS != BackedgeTCExt && SCEVRHS != SCEVTripCountExt) {
342 if (SCEVRHS == BackedgeTCExt || SCEVRHS == BackedgeTakenCount) {
346 IterationInstructions);
358 if (!TripCountInst) {
363 SE->
getSCEV(TripCountInst->getOperand(0)) != SCEVTripCount) {
364 LLVM_DEBUG(
dbgs() <<
"Could not find valid extended trip count\n");
376 LLVM_DEBUG(
dbgs() <<
"Finding components of loop: " << L->getName() <<
"\n");
378 if (!L->isLoopSimplifyForm()) {
385 if (!L->isCanonical(*SE)) {
393 if (L->getExitingBlock() != Latch) {
401 InductionPHI = L->getInductionVariable(*SE);
418 ICmpInst *Compare = L->getLatchCmpInst();
419 if (!Compare || !IsValidPredicate(Compare->getUnsignedPredicate()) ||
420 Compare->hasNUsesOrMore(2)) {
425 IterationInstructions.
insert(BackBranch);
427 IterationInstructions.
insert(Compare);
446 Value *
RHS = Compare->getOperand(1);
468 SafeOuterPHIs.
insert(FI.OuterInductionPHI);
475 if (&InnerPHI == FI.InnerInductionPHI)
477 if (FI.isNarrowInductionPhi(&InnerPHI))
482 assert(InnerPHI.getNumIncomingValues() == 2);
483 Value *PreHeaderValue =
486 InnerPHI.getIncomingValueForBlock(FI.InnerLoop->
getLoopLatch());
512 dbgs() <<
"LCSSA PHI incoming value does not match latch value\n");
519 SafeOuterPHIs.
insert(OuterPHI);
520 FI.InnerPHIsToTransform.insert(&InnerPHI);
524 if (FI.isNarrowInductionPhi(&OuterPHI))
526 if (!SafeOuterPHIs.
count(&OuterPHI)) {
527 LLVM_DEBUG(
dbgs() <<
"found unsafe PHI in outer loop: "; OuterPHI.dump());
553 LLVM_DEBUG(
dbgs() <<
"Cannot flatten because instruction may have "
562 if (IterationInstructions.
count(&
I))
577 RepeatedInstrCost += Cost;
581 LLVM_DEBUG(
dbgs() <<
"Cost of instructions that will be repeated: "
582 << RepeatedInstrCost <<
"\n");
585 if (RepeatedInstrCost > FI.Opts.loop_flatten_cost_threshold) {
586 LLVM_DEBUG(
dbgs() <<
"checkOuterLoopInsts: not profitable, bailing.\n");
607 if (!FI.checkInnerInductionPhiUsers(ValidOuterPHIUses))
612 if (!FI.checkOuterInductionPhiUsers(ValidOuterPHIUses))
616 dbgs() <<
"Found " << FI.LinearIVUses.
size()
617 <<
" value(s) that can be replaced:\n";
618 for (
Value *V : FI.LinearIVUses) {
633 if (FI.Opts.loop_flatten_assume_no_overflow)
639 FI.InnerTripCount, FI.OuterTripCount,
646 for (
Value *GEPUser :
GEP->users()) {
657 if (
GEP->isInBounds() &&
658 GEPOperand->getType()->getIntegerBitWidth() >=
659 DL.getPointerTypeSizeInBits(
GEP->getType())) {
661 dbgs() <<
"use of linear IV would be UB if overflow occurred: ";
671 for (
Value *V : FI.LinearIVUses) {
673 if (
GEP->getNumIndices() == 1 && CheckGEP(
GEP,
GEP->getOperand(1)))
677 if (CheckGEP(
GEP, V))
689 FI.InnerInductionPHI, FI.InnerTripCount,
690 FI.InnerIncrement, FI.InnerBranch, SE, FI.Widened))
693 FI.OuterInductionPHI, FI.OuterTripCount,
694 FI.OuterIncrement, FI.OuterBranch, SE, FI.Widened))
712 if (FI.InnerInductionPHI->
getType() != FI.OuterInductionPHI->
getType())
735 LLVM_DEBUG(
dbgs() <<
"Checks all passed, doing the transformation\n");
741 Remark <<
"Flattened into outer loop";
745 if (!FI.NewTripCount) {
746 FI.NewTripCount = BinaryOperator::CreateMul(
747 FI.InnerTripCount, FI.OuterTripCount,
"flatten.tripcount",
750 FI.NewTripCount->
dump());
772 Term->eraseFromParent();
782 for (
Value *V : FI.LinearIVUses) {
783 Value *OuterValue = FI.OuterInductionPHI;
785 OuterValue = Builder.CreateTrunc(FI.OuterInductionPHI, V->
getType(),
797 Builder.CreateGEP(
GEP->getSourceElementType(),
Base, OuterValue,
799 GEP->isInBounds() && InnerGEP->isInBounds());
812 U->markLoopAsDeleted(*FI.InnerLoop, FI.InnerLoop->
getName());
813 LI->
erase(FI.InnerLoop);
824 if (!FI.Opts.loop_flatten_widen_iv) {
831 auto &
DL = M->getDataLayout();
832 auto *InnerType = FI.InnerInductionPHI->
getType();
833 auto *OuterType = FI.OuterInductionPHI->
getType();
834 unsigned MaxLegalSize =
DL.getLargestLegalIntTypeSizeInBits();
835 auto *MaxLegalType =
DL.getLargestLegalIntType(M->getContext());
840 if (InnerType != OuterType ||
841 InnerType->getScalarSizeInBits() >= MaxLegalSize ||
842 MaxLegalType->getScalarSizeInBits() <
843 InnerType->getScalarSizeInBits() * 2) {
850 unsigned ElimExt = 0;
851 unsigned Widened = 0;
855 createWideIV(WideIV, LI, SE, Rewriter, DT, DeadInsts, ElimExt, Widened,
867 if (!CreateWideIV({FI.InnerInductionPHI, MaxLegalType,
false},
Deleted))
872 FI.InnerPHIsToTransform.insert(FI.InnerInductionPHI);
874 if (!CreateWideIV({FI.OuterInductionPHI, MaxLegalType,
false},
Deleted))
877 assert(Widened &&
"Widened IV expected");
881 FI.NarrowInnerInductionPHI = FI.InnerInductionPHI;
882 FI.NarrowOuterInductionPHI = FI.OuterInductionPHI;
894 dbgs() <<
"Loop flattening running on outer loop "
916 if (FI.Widened && !CanFlatten)
930 LLVM_DEBUG(
dbgs() <<
"Multiply would always overflow, so not profitable\n");
935 if (!FI.Opts.loop_flatten_version_loops) {
936 LLVM_DEBUG(
dbgs() <<
"Multiply might overflow, not flattening\n");
938 }
else if (!
DL.isLegalInteger(
944 dbgs() <<
"Can't check overflow efficiently, not flattening\n");
947 LLVM_DEBUG(
dbgs() <<
"Multiply might overflow, versioning loop\n");
954 LoopVersioning LVer(LAI, Checks, FI.OuterLoop, LI, DT, SE, MSSAU);
961 "Expected branch condition to be false");
964 Intrinsic::umul_with_overflow, FI.OuterTripCount->
getType(),
965 {FI.OuterTripCount, FI.InnerTripCount},
966 nullptr,
"flatten.mul");
967 FI.NewTripCount = Builder.CreateExtractValue(
Call, 0,
"flatten.tripcount");
968 Value *Overflow = Builder.CreateExtractValue(
Call, 1,
"flatten.overflow");
971 LLVM_DEBUG(
dbgs() <<
"Multiply cannot overflow, modifying loop in-place\n");
983 std::optional<MemorySSAUpdater> MSSAU;
1000 FlattenInfo FI(OuterLoop, InnerLoop);
1003 MSSAU ? &*MSSAU :
nullptr, LAIM.
getInfo(*OuterLoop));
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
MachineBasicBlock MachineBasicBlock::iterator DebugLoc DL
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
Module.h This file contains the declarations for the Module class.
static bool CanWidenIV(FlattenInfo &FI, DominatorTree *DT, LoopInfo *LI, ScalarEvolution *SE, AssumptionCache *AC, const TargetTransformInfo *TTI)
static bool findLoopComponents(Loop *L, SmallPtrSetImpl< Instruction * > &IterationInstructions, PHINode *&InductionPHI, Value *&TripCount, BinaryOperator *&Increment, CondBrInst *&BackBranch, ScalarEvolution *SE, bool IsWidened)
static bool setLoopComponents(Value *&TC, Value *&TripCount, BinaryOperator *&Increment, SmallPtrSetImpl< Instruction * > &IterationInstructions)
static bool DoFlattenLoopPair(FlattenInfo &FI, DominatorTree *DT, LoopInfo *LI, ScalarEvolution *SE, AssumptionCache *AC, const TargetTransformInfo *TTI, LPMUpdater *U, MemorySSAUpdater *MSSAU)
static bool FlattenLoopPair(FlattenInfo &FI, DominatorTree *DT, LoopInfo *LI, ScalarEvolution *SE, AssumptionCache *AC, const TargetTransformInfo *TTI, LPMUpdater *U, MemorySSAUpdater *MSSAU, const LoopAccessInfo &LAI)
static bool checkIVUsers(FlattenInfo &FI)
static bool CanFlattenLoopPair(FlattenInfo &FI, DominatorTree *DT, LoopInfo *LI, ScalarEvolution *SE, AssumptionCache *AC, const TargetTransformInfo *TTI)
static bool verifyTripCount(Value *RHS, Loop *L, SmallPtrSetImpl< Instruction * > &IterationInstructions, PHINode *&InductionPHI, Value *&TripCount, BinaryOperator *&Increment, CondBrInst *&BackBranch, ScalarEvolution *SE, bool IsWidened)
static OverflowResult checkOverflow(FlattenInfo &FI, DominatorTree *DT, AssumptionCache *AC)
static bool checkPHIs(FlattenInfo &FI, const TargetTransformInfo *TTI)
static bool checkOuterLoopInsts(FlattenInfo &FI, SmallPtrSetImpl< Instruction * > &IterationInstructions, const TargetTransformInfo *TTI)
This file defines the interface for the loop nest analysis.
This header provides classes for managing a pipeline of passes over loops in LLVM IR.
This file defines the 'Statistic' class, which is designed to be an easy way to expose various metric...
#define STATISTIC(VARNAME, DESC)
virt reg Virtual Register Rewriter
Represent a constant reference to an array (0 or more elements consecutively in memory),...
A cache of @llvm.assume calls within a function.
LLVM Basic Block Representation.
iterator_range< const_phi_iterator > phis() const
Returns a range that iterates over the phis in the basic block.
const Function * getParent() const
Return the enclosing method, or null if none.
const Instruction * getTerminator() const LLVM_READONLY
Returns the terminator instruction; assumes that the block is well-formed.
Predicate
This enumeration lists the possible predicates for CmpInst subclasses.
@ ICMP_ULT
unsigned less than
Conditional Branch instruction.
void setCondition(Value *V)
Value * getCondition() const
This is the shared class of boolean and integer constants.
const APInt & getValue() const
Return the constant as an APInt value reference.
A parsed version of the target data layout string in and methods for querying it.
void deleteEdge(NodeT *From, NodeT *To)
Inform the dominator tree about a CFG edge deletion and update the tree.
Concrete subclass of DominatorTreeBase that is used to compute a normal dominator tree.
LLVM_ABI bool dominates(const BasicBlock *BB, const Use &U) const
Return true if the (end of the) basic block BB dominates the use U.
an instruction for type-safe pointer arithmetic to access elements of arrays and structs
Module * getParent()
Get the module that this global value is contained inside of...
This instruction compares its operands according to the predicate given to the constructor.
This provides a uniform API for creating instructions and inserting them into a basic block: either a...
LLVM_ABI BasicBlock * getSuccessor(unsigned Idx) const LLVM_READONLY
Return the specified successor. This instruction must be a terminator.
void setDebugLoc(DebugLoc Loc)
Set the debug location information for this instruction.
This class provides an interface for updating the loop pass manager based on mutations to the loop ne...
LLVM_ABI const LoopAccessInfo & getInfo(Loop &L, bool AllowPartial=false)
Drive the analysis of memory accesses in the loop.
bool contains(const LoopT *L) const
Return true if the specified loop is contained within this loop.
BlockT * getLoopLatch() const
If there is a single latch block for this loop, return it.
BlockT * getHeader() const
BlockT * getExitBlock() const
If getExitBlocks would return exactly one block, return that block.
BlockT * getLoopPreheader() const
If there is a preheader for this loop, return it.
ArrayRef< BlockT * > getBlocks() const
Get a list of the basic blocks which make up this loop.
BlockT * getExitingBlock() const
If getExitingBlocks would return exactly one block, return that block.
LoopT * getParentLoop() const
Return the parent loop if it exists or nullptr for top level loops.
LLVM_ABI PreservedAnalyses run(LoopNest &LN, LoopAnalysisManager &LAM, LoopStandardAnalysisResults &AR, LPMUpdater &U)
LLVM_ABI void erase(Loop *L)
Update LoopInfo after removing the last backedge from a loop.
This class represents a loop nest and can be used to query its properties.
ArrayRef< Loop * > getLoops() const
Get the loops in the nest.
This class emits a version of the loop where run-time checks ensure that may-alias pointers can't ove...
void versionLoop()
Performs the CFG manipulation part of versioning the loop including the DominatorTree,...
Represents a single loop in the control flow graph.
DebugLoc getStartLoc() const
Return the debug location of the start of this loop.
bool isLoopInvariant(const Value *V) const
Return true if the specified value is loop invariant.
StringRef getName() const
An analysis that produces MemorySSA for a function.
LLVM_ABI void removeEdge(BasicBlock *From, BasicBlock *To)
Update the MemoryPhi in To following an edge deletion between From and To.
LLVM_ABI void verifyMemorySSA(VerificationLevel=VerificationLevel::Fast) const
Verify that MemorySSA is self consistent (IE definitions dominate all uses, uses appear in the right ...
A Module instance is used to store all the information related to an LLVM module.
LLVM_ABI Value * removeIncomingValue(unsigned Idx, bool DeletePHIIfEmpty=true)
Remove an incoming value.
Value * getIncomingValueForBlock(const BasicBlock *BB) const
LLVM_ABI Value * hasConstantValue() const
If the specified PHI node always merges together the same value, return the value,...
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.
This class uses information about analyze scalars to rewrite expressions in canonical form.
This class represents an analyzed expression in the program.
Type * getType() const
Return the LLVM type of this SCEV expression.
The main scalar evolution driver.
LLVM_ABI const SCEV * getZeroExtendExpr(SCEVUse Op, Type *Ty, unsigned Depth=0)
LLVM_ABI const SCEV * getBackedgeTakenCount(const Loop *L, ExitCountKind Kind=Exact)
If the specified loop has a predictable backedge-taken count, return it, otherwise return a SCEVCould...
LLVM_ABI const SCEV * getSCEV(Value *V)
Return a SCEV expression for the full generality of the specified expression.
LLVM_ABI const SCEV * getTripCountFromExitCount(const SCEV *ExitCount)
A version of getTripCountFromExitCount below which always picks an evaluation type which can not resu...
LLVM_ABI void forgetLoop(const Loop *L)
This method should be called by the client when it has changed a loop in a way that may effect Scalar...
LLVM_ABI void forgetBlockAndLoopDispositions(Value *V=nullptr)
Called when the client has changed the disposition of values in a loop or block.
A templated base class for SmallPtrSet which provides the typesafe interface that is common across al...
size_type count(ConstPtrType Ptr) const
count - Return 1 if the specified pointer is in the set, 0 otherwise.
std::pair< iterator, bool > insert(PtrType Ptr)
Inserts Ptr if and only if there is no element in the container equal to Ptr.
SmallPtrSet - This class implements a set which is optimized for holding SmallSize or less elements.
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
LLVM_ABI unsigned getScalarSizeInBits() const LLVM_READONLY
If this is a vector type, return the getPrimitiveSizeInBits value for the element type.
Unconditional Branch instruction.
static UncondBrInst * Create(BasicBlock *Target, InsertPosition InsertBefore=nullptr)
BasicBlock * getSuccessor(unsigned i=0) const
LLVM Value Representation.
Type * getType() const
All values are typed, get the type of this value.
LLVM_ABI void replaceAllUsesWith(Value *V)
Change all uses of this to point to a new Value.
LLVMContext & getContext() const
All values hold a context through their type.
iterator_range< user_iterator > users()
LLVM_ABI StringRef getName() const
Return a constant reference to the value's name.
LLVM_ABI void dump() const
Support for debugging, callable in GDB: V->dump()
const ParentTy * getParent() const
self_iterator getIterator()
CastInst_match< OpTy, TruncInst > m_Trunc(const OpTy &Op)
Matches Trunc.
bool match(Val *V, const Pattern &P)
specificval_ty m_Specific(const Value *V)
Match if we have a specific specified value.
auto m_Value()
Match an arbitrary value and ignore it.
auto m_GEP(const OperandTypes &...Ops)
Matches GetElementPtrInst.
BinaryOp_match< LHS, RHS, Instruction::Add, true > m_c_Add(const LHS &L, const RHS &R)
Matches a Add with LHS and RHS in either order.
is_zero m_Zero()
Match any null constant or a vector with all elements equal to 0.
BinaryOp_match< LHS, RHS, Instruction::Mul, true > m_c_Mul(const LHS &L, const RHS &R)
Matches a Mul with LHS and RHS in either order.
@ User
could "use" a pointer
Add a small namespace to avoid name clashes with the classes used in the streaming interface.
NodeAddr< PhiNode * > Phi
This is an optimization pass for GlobalISel generic memory operations.
@ NeverOverflows
Never overflows.
@ AlwaysOverflowsHigh
Always overflows in the direction of signed/unsigned max value.
@ AlwaysOverflowsLow
Always overflows in the direction of signed/unsigned min value.
@ MayOverflow
May or may not overflow.
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
LLVM_ABI PHINode * createWideIV(const WideIVInfo &WI, LoopInfo *LI, ScalarEvolution *SE, SCEVExpander &Rewriter, DominatorTree *DT, SmallVectorImpl< WeakTrackingVH > &DeadInsts, unsigned &NumElimExt, unsigned &NumWidened, bool HasGuards, bool UsePostIncrementRanges)
Widen Induction Variables - Extend the width of an IV to cover its widest uses.
LLVM_ABI bool isGuaranteedToExecuteForEveryIteration(const Instruction *I, const Loop *L)
Return true if this function can prove that the instruction I is executed for every iteration of the ...
LLVM_ABI bool isSafeToSpeculativelyExecute(const Instruction *I, const Instruction *CtxI=nullptr, AssumptionCache *AC=nullptr, const DominatorTree *DT=nullptr, const TargetLibraryInfo *TLI=nullptr, bool UseVariableInfo=true, bool IgnoreUBImplyingAttrs=true)
Return true if the instruction does not have any effects besides calculating the result and does not ...
RelativeUniformCounterPtr ValuesPtrExpr VTableAddr Value
AnalysisManager< Loop, LoopStandardAnalysisResults & > LoopAnalysisManager
The loop analysis manager.
LLVM_ABI OverflowResult computeOverflowForUnsignedMul(const Value *LHS, const Value *RHS, const SimplifyQuery &SQ, bool IsNSW=false)
LLVM_ABI raw_ostream & dbgs()
dbgs() - This returns a reference to a raw_ostream for debugging messages.
LLVM_ABI bool RecursivelyDeleteDeadPHINode(PHINode *PN, const TargetLibraryInfo *TLI=nullptr, MemorySSAUpdater *MSSAU=nullptr, SmallPtrSetImpl< PHINode * > *KnownNonDeadPHIs=nullptr)
If the specified value is an effectively dead PHI node, due to being a def-use chain of single-use no...
bool isa(const From &Val)
isa<X> - Return true if the parameter to the template is an instance of one of the template type argu...
@ Global
Append to llvm.global_dtors.
LLVM_ABI bool VerifyMemorySSA
Enables verification of MemorySSA.
auto count_if(R &&Range, UnaryPredicate P)
Wrapper function around std::count_if to count the number of times an element satisfying a given pred...
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
LLVM_ABI PreservedAnalyses getLoopPassPreservedAnalyses()
Returns the minimum set of Analyses that all loop passes must preserve.
@ Increment
Incrementally increasing token ID.
The adaptor from a function pass to a loop pass computes these analyses and makes them available to t...
TargetTransformInfo & TTI
Collect information about induction variables that are used by sign/zero extend operations.