-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[VectorCombine] Add support for zext/sext/trunc to shuffleToIdentity #92696
Conversation
@llvm/pr-subscribers-llvm-transforms Author: David Green (davemgreen) ChangesThis is one of the simple additions to shuffleToIdentity that help it look through intermediate zext/sext instructions. The other change here is the removal of a check that both operands of the original shuffle are instructions, which is a relic from a previous version. Full diff: https://github.com/llvm/llvm-project/pull/92696.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 15deaf908422d..751780b8794b8 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -1673,8 +1673,7 @@ bool VectorCombine::foldShuffleOfShuffles(Instruction &I) {
// do so.
bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
auto *Ty = dyn_cast<FixedVectorType>(I.getType());
- if (!Ty || !isa<Instruction>(I.getOperand(0)) ||
- !isa<Instruction>(I.getOperand(1)))
+ if (!Ty)
return false;
using InstLane = std::pair<Value *, int>;
@@ -1773,7 +1772,9 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
!cast<BinaryOperator>(Item[0].first)->isIntDivRem()) {
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 0));
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 1));
- } else if (isa<UnaryOperator>(Item[0].first)) {
+ } else if (isa<UnaryOperator>(Item[0].first) ||
+ isa<TruncInst>(Item[0].first) || isa<ZExtInst>(Item[0].first) ||
+ isa<SExtInst>(Item[0].first)) {
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 0));
} else if (auto *II = dyn_cast<IntrinsicInst>(Item[0].first);
II && isTriviallyVectorizable(II->getIntrinsicID())) {
@@ -1834,6 +1835,9 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
if (auto BI = dyn_cast<BinaryOperator>(I))
return Builder.CreateBinOp((Instruction::BinaryOps)BI->getOpcode(),
Ops[0], Ops[1]);
+ if (auto CI = dyn_cast<CastInst>(I))
+ return Builder.CreateCast((Instruction::CastOps)CI->getOpcode(), Ops[0],
+ DstTy);
if (II)
return Builder.CreateIntrinsic(DstTy, II->getIntrinsicID(), Ops);
assert(isa<UnaryInstruction>(I) &&
diff --git a/llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll b/llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
index bb333941abf70..441d797597da6 100644
--- a/llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
+++ b/llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
@@ -432,19 +432,10 @@ define <8 x half> @fma(<8 x half> %a, <8 x half> %b, <8 x half> %c) {
define void @exttrunc(<8 x i32> %a, <8 x i32> %b, ptr %p) {
; CHECK-LABEL: @exttrunc(
-; CHECK-NEXT: [[AB:%.*]] = shufflevector <8 x i32> [[A:%.*]], <8 x i32> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[AT:%.*]] = shufflevector <8 x i32> [[A]], <8 x i32> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[BB:%.*]] = shufflevector <8 x i32> [[B:%.*]], <8 x i32> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[BT:%.*]] = shufflevector <8 x i32> [[B]], <8 x i32> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[AB1:%.*]] = zext <4 x i32> [[AB]] to <4 x i64>
-; CHECK-NEXT: [[AT1:%.*]] = zext <4 x i32> [[AT]] to <4 x i64>
-; CHECK-NEXT: [[BB1:%.*]] = sext <4 x i32> [[BB]] to <4 x i64>
-; CHECK-NEXT: [[BT1:%.*]] = sext <4 x i32> [[BT]] to <4 x i64>
-; CHECK-NEXT: [[ABB:%.*]] = add <4 x i64> [[AB1]], [[BB1]]
-; CHECK-NEXT: [[ABT:%.*]] = add <4 x i64> [[AT1]], [[BT1]]
-; CHECK-NEXT: [[ABB1:%.*]] = trunc <4 x i64> [[ABB]] to <4 x i32>
-; CHECK-NEXT: [[ABT1:%.*]] = trunc <4 x i64> [[ABT]] to <4 x i32>
-; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x i32> [[ABB1]], <4 x i32> [[ABT1]], <8 x i32> <i32 0, i32 4, i32 1, i32 5, i32 2, i32 6, i32 3, i32 7>
+; CHECK-NEXT: [[TMP1:%.*]] = zext <8 x i32> [[A:%.*]] to <8 x i64>
+; CHECK-NEXT: [[TMP2:%.*]] = sext <8 x i32> [[B:%.*]] to <8 x i64>
+; CHECK-NEXT: [[TMP3:%.*]] = add <8 x i64> [[TMP1]], [[TMP2]]
+; CHECK-NEXT: [[R:%.*]] = trunc <8 x i64> [[TMP3]] to <8 x i32>
; CHECK-NEXT: store <8 x i32> [[R]], ptr [[P:%.*]], align 32
; CHECK-NEXT: ret void
;
@@ -467,17 +458,9 @@ define void @exttrunc(<8 x i32> %a, <8 x i32> %b, ptr %p) {
define void @zext(<8 x i16> %a, <8 x i16> %b, ptr %p) {
; CHECK-LABEL: @zext(
-; CHECK-NEXT: [[AB:%.*]] = shufflevector <8 x i16> [[A:%.*]], <8 x i16> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[AT:%.*]] = shufflevector <8 x i16> [[A]], <8 x i16> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[BB:%.*]] = shufflevector <8 x i16> [[B:%.*]], <8 x i16> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[BT:%.*]] = shufflevector <8 x i16> [[B]], <8 x i16> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[AB1:%.*]] = zext <4 x i16> [[AB]] to <4 x i32>
-; CHECK-NEXT: [[AT1:%.*]] = zext <4 x i16> [[AT]] to <4 x i32>
-; CHECK-NEXT: [[BB1:%.*]] = zext <4 x i16> [[BB]] to <4 x i32>
-; CHECK-NEXT: [[BT1:%.*]] = zext <4 x i16> [[BT]] to <4 x i32>
-; CHECK-NEXT: [[ABB:%.*]] = add <4 x i32> [[AB1]], [[BB1]]
-; CHECK-NEXT: [[ABT:%.*]] = add <4 x i32> [[AT1]], [[BT1]]
-; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x i32> [[ABB]], <4 x i32> [[ABT]], <8 x i32> <i32 0, i32 4, i32 1, i32 5, i32 2, i32 6, i32 3, i32 7>
+; CHECK-NEXT: [[TMP1:%.*]] = zext <8 x i16> [[A:%.*]] to <8 x i32>
+; CHECK-NEXT: [[TMP2:%.*]] = zext <8 x i16> [[B:%.*]] to <8 x i32>
+; CHECK-NEXT: [[R:%.*]] = add <8 x i32> [[TMP1]], [[TMP2]]
; CHECK-NEXT: store <8 x i32> [[R]], ptr [[P:%.*]], align 32
; CHECK-NEXT: ret void
;
@@ -498,17 +481,9 @@ define void @zext(<8 x i16> %a, <8 x i16> %b, ptr %p) {
define void @sext(<8 x i16> %a, <8 x i16> %b, ptr %p) {
; CHECK-LABEL: @sext(
-; CHECK-NEXT: [[AB:%.*]] = shufflevector <8 x i16> [[A:%.*]], <8 x i16> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[AT:%.*]] = shufflevector <8 x i16> [[A]], <8 x i16> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[BB:%.*]] = shufflevector <8 x i16> [[B:%.*]], <8 x i16> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[BT:%.*]] = shufflevector <8 x i16> [[B]], <8 x i16> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[AB1:%.*]] = sext <4 x i16> [[AB]] to <4 x i32>
-; CHECK-NEXT: [[AT1:%.*]] = sext <4 x i16> [[AT]] to <4 x i32>
-; CHECK-NEXT: [[BB1:%.*]] = sext <4 x i16> [[BB]] to <4 x i32>
-; CHECK-NEXT: [[BT1:%.*]] = sext <4 x i16> [[BT]] to <4 x i32>
-; CHECK-NEXT: [[ABB:%.*]] = add <4 x i32> [[AB1]], [[BB1]]
-; CHECK-NEXT: [[ABT:%.*]] = add <4 x i32> [[AT1]], [[BT1]]
-; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x i32> [[ABB]], <4 x i32> [[ABT]], <8 x i32> <i32 0, i32 4, i32 1, i32 5, i32 2, i32 6, i32 3, i32 7>
+; CHECK-NEXT: [[TMP1:%.*]] = sext <8 x i16> [[A:%.*]] to <8 x i32>
+; CHECK-NEXT: [[TMP2:%.*]] = sext <8 x i16> [[B:%.*]] to <8 x i32>
+; CHECK-NEXT: [[R:%.*]] = add <8 x i32> [[TMP1]], [[TMP2]]
; CHECK-NEXT: store <8 x i32> [[R]], ptr [[P:%.*]], align 32
; CHECK-NEXT: ret void
;
@@ -567,11 +542,7 @@ define void @zext_types(<8 x i16> %a, <8 x i32> %b, ptr %p) {
define void @trunc(<8 x i64> %a, <8 x i64> %b, ptr %p) {
; CHECK-LABEL: @trunc(
-; CHECK-NEXT: [[AB:%.*]] = shufflevector <8 x i64> [[A:%.*]], <8 x i64> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEXT: [[AT:%.*]] = shufflevector <8 x i64> [[A]], <8 x i64> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
-; CHECK-NEXT: [[ABB1:%.*]] = trunc <4 x i64> [[AB]] to <4 x i32>
-; CHECK-NEXT: [[ABT1:%.*]] = trunc <4 x i64> [[AT]] to <4 x i32>
-; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x i32> [[ABB1]], <4 x i32> [[ABT1]], <8 x i32> <i32 0, i32 4, i32 1, i32 5, i32 2, i32 6, i32 3, i32 7>
+; CHECK-NEXT: [[R:%.*]] = trunc <8 x i64> [[A:%.*]] to <8 x i32>
; CHECK-NEXT: store <8 x i32> [[R]], ptr [[P:%.*]], align 32
; CHECK-NEXT: ret void
;
@@ -745,13 +716,11 @@ entry:
define <4 x i8> @singleop(<4 x i8> %a, <4 x i8> %b) {
; CHECK-LABEL: @singleop(
-; CHECK-NEXT: [[A1:%.*]] = shufflevector <4 x i8> [[A:%.*]], <4 x i8> poison, <4 x i32> <i32 3, i32 2, i32 1, i32 0>
-; CHECK-NEXT: [[B1:%.*]] = shufflevector <4 x i8> [[B:%.*]], <4 x i8> poison, <4 x i32> zeroinitializer
-; CHECK-NEXT: [[A2:%.*]] = zext <4 x i8> [[A1]] to <4 x i16>
-; CHECK-NEXT: [[B2:%.*]] = zext <4 x i8> [[B1]] to <4 x i16>
-; CHECK-NEXT: [[AB:%.*]] = add <4 x i16> [[A2]], [[B2]]
-; CHECK-NEXT: [[T:%.*]] = trunc <4 x i16> [[AB]] to <4 x i8>
-; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x i8> [[T]], <4 x i8> poison, <4 x i32> <i32 3, i32 2, i32 1, i32 0>
+; CHECK-NEXT: [[TMP1:%.*]] = shufflevector <4 x i8> [[B:%.*]], <4 x i8> poison, <4 x i32> zeroinitializer
+; CHECK-NEXT: [[TMP2:%.*]] = zext <4 x i8> [[A:%.*]] to <4 x i16>
+; CHECK-NEXT: [[TMP3:%.*]] = zext <4 x i8> [[TMP1]] to <4 x i16>
+; CHECK-NEXT: [[TMP4:%.*]] = add <4 x i16> [[TMP2]], [[TMP3]]
+; CHECK-NEXT: [[R:%.*]] = trunc <4 x i16> [[TMP4]] to <4 x i8>
; CHECK-NEXT: ret <4 x i8> [[R]]
;
%a1 = shufflevector <4 x i8> %a, <4 x i8> poison, <4 x i32> <i32 3, i32 2, i32 1, i32 0>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
I did a partial review of the existing code, and my suggestions are along the lines of the following diff. I think it would be nice if you could improve the existing code as an NFC patch before landing this patch.
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8573a8adf53b..52830d7ed462 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -1686,7 +1686,7 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
int M = SV->getMaskValue(Lane);
if (M < 0)
return {nullptr, PoisonMaskElem};
- else if (M < (int)NumElts) {
+ if (static_cast<unsigned>(M) < NumElts) {
V = SV->getOperand(0);
Lane = M;
} else {
@@ -1700,12 +1700,12 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
auto GenerateInstLaneVectorFromOperand =
[&LookThroughShuffles](ArrayRef<InstLane> Item, int Op) {
SmallVector<InstLane> NItem;
- for (InstLane V : Item) {
- NItem.emplace_back(
- !V.first
- ? InstLane{nullptr, PoisonMaskElem}
- : LookThroughShuffles(
- cast<Instruction>(V.first)->getOperand(Op), V.second));
+ for (InstLane IL : Item) {
+ auto [V, Lane] = IL;
+ // The caller is responsible for ensuring that this is always an Instruction.
+ auto *I = cast<Instruction>(V);
+ InstLane ToInsert = V ? LookThroughShuffles(I->getOperand(Op), Lane) : IL;
+ NItem.emplace_back(ToInsert);
}
return NItem;
};
@@ -1721,57 +1721,66 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
while (!Worklist.empty()) {
SmallVector<InstLane> Item = Worklist.pop_back_val();
- if (++NumVisited > MaxInstrsToScan)
+ if (NumVisited++ > MaxInstrsToScan)
return false;
+ auto [FrontV, FrontLane] = Item.front();
+
// If we found an undef first lane then bail out to keep things simple.
- if (!Item[0].first)
+ if (!FrontV)
return false;
// Look for an identity value.
- if (Item[0].second == 0 && Item[0].first->getType() == Ty &&
- all_of(drop_begin(enumerate(Item)), [&](const auto &E) {
- return !E.value().first || (E.value().first == Item[0].first &&
- E.value().second == (int)E.index());
+ if (!FrontLane && FrontV->getType() == Ty &&
+ all_of(drop_begin(enumerate(Item)), [Item](const auto &I) {
+ auto [FrontV, _] = Item.front();
+ auto [Idx, E] = I;
+ auto [V, Lane] = E;
+ return !V || (V == FrontV && Lane == (int)Idx);
})) {
- IdentityLeafs.insert(Item[0].first);
+ IdentityLeafs.insert(FrontV);
continue;
}
+
// Look for a splat value.
- if (all_of(drop_begin(Item), [&](InstLane &IL) {
- return !IL.first ||
- (IL.first == Item[0].first && IL.second == Item[0].second);
+ if (all_of(drop_begin(Item), [Item](InstLane &IL) {
+ auto [FrontV, FrontLane] = Item.front();
+ auto [V, Lane] = IL;
+ return !V ||
+ (V == FrontV && Lane == FrontLane);
})) {
- SplatLeafs.insert(Item[0].first);
+ SplatLeafs.insert(FrontV);
continue;
}
// We need each element to be the same type of value, and check that each
// element has a single use.
- if (!all_of(drop_begin(Item), [&](InstLane IL) {
- if (!IL.first)
+ if (!all_of(drop_begin(Item), [Item](InstLane IL) {
+ auto [FrontV, _] = Item.front();
+ auto [V, _Lane] = IL;
+ if (!V)
return true;
- if (auto *I = dyn_cast<Instruction>(IL.first); I && !I->hasOneUse())
+ if (auto *I = dyn_cast<Instruction>(V); I && !I->hasOneUse())
return false;
- if (IL.first->getValueID() != Item[0].first->getValueID())
+ if (V->getValueID() != FrontV->getValueID())
return false;
- if (isa<CallInst>(IL.first) && !isa<IntrinsicInst>(IL.first))
+ if (isa<CallInst>(V) && !isa<IntrinsicInst>(V))
return false;
- auto *II = dyn_cast<IntrinsicInst>(IL.first);
+ auto *II = dyn_cast<IntrinsicInst>(V);
return !II ||
- (isa<IntrinsicInst>(Item[0].first) &&
+ (isa<IntrinsicInst>(FrontV) &&
II->getIntrinsicID() ==
- cast<IntrinsicInst>(Item[0].first)->getIntrinsicID());
+ cast<IntrinsicInst>(FrontV)->getIntrinsicID());
}))
return false;
// Check the operator is one that we support. We exclude div/rem in case
// they hit UB from poison lanes.
- if (isa<BinaryOperator>(Item[0].first) &&
- !cast<BinaryOperator>(Item[0].first)->isIntDivRem()) {
+ if (isa<BinaryOperator>(FrontV) &&
+ !cast<BinaryOperator>(FrontV)->isIntDivRem()) {
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 0));
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 1));
- } else if (isa<UnaryOperator>(Item[0].first)) {
+ } else if (isa<UnaryOperator>(FrontV)) {
Worklist.push_back(GenerateInstLaneVectorFromOperand(Item, 0));
} else {
return false;
@@ -1790,7 +1799,7 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
return Item[0].first;
}
if (SplatLeafs.contains(Item[0].first)) {
- if (auto ILI = dyn_cast<Instruction>(Item[0].first))
+ if (auto *ILI = dyn_cast<Instruction>(Item[0].first))
Builder.SetInsertPoint(*ILI->getInsertionPointAfterDef());
else if (isa<Argument>(Item[0].first))
Builder.SetInsertPointPastAllocas(I.getParent()->getParent());
@@ -1803,7 +1812,7 @@ bool VectorCombine::foldShuffleToIdentity(Instruction &I) {
for (unsigned Idx = 0, E = I->getNumOperands(); Idx < E; Idx++)
Ops[Idx] = Generate(GenerateInstLaneVectorFromOperand(Item, Idx));
Builder.SetInsertPoint(I);
- if (auto BI = dyn_cast<BinaryOperator>(I))
+ if (auto *BI = dyn_cast<BinaryOperator>(I))
return Builder.CreateBinOp((Instruction::BinaryOps)BI->getOpcode(),
Ops[0], Ops[1]);
assert(isa<UnaryInstruction>(I) &&
--
2.45.1
I additionally think the long lambdas make it hard to reason about the code. Might be good to split them out into static functions. |
5fa0014
to
d2ee975
Compare
Do you want to put together a patch for them? It is hard to see why the cast is always correct but some of the other changes look decent. |
Sure, I'll also try to simplify the algorithm. |
rebase? |
Yeah will do. |
d2ee975
to
0ba97b3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seem to be some underlying issues, not directly related to your patch.
llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
Outdated
Show resolved
Hide resolved
llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
Outdated
Show resolved
Hide resolved
llvm/test/Transforms/VectorCombine/AArch64/shuffletoidentity.ll
Outdated
Show resolved
Hide resolved
@@ -1742,6 +1742,9 @@ static Value *generateNewInstTree(ArrayRef<InstLane> Item, FixedVectorType *Ty, | |||
if (auto *BI = dyn_cast<BinaryOperator>(I)) | |||
return Builder.CreateBinOp((Instruction::BinaryOps)BI->getOpcode(), Ops[0], | |||
Ops[1]); | |||
if (auto CI = dyn_cast<CastInst>(I)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto *CI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After this nit is fixed, this patch LGTM.
@@ -1753,8 +1756,7 @@ static Value *generateNewInstTree(ArrayRef<InstLane> Item, FixedVectorType *Ty, | |||
// do so. | |||
bool VectorCombine::foldShuffleToIdentity(Instruction &I) { | |||
auto *Ty = dyn_cast<FixedVectorType>(I.getType()); | |||
if (!Ty || !isa<Instruction>(I.getOperand(0)) || | |||
!isa<Instruction>(I.getOperand(1))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this an unrelated change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure - I can split it out. It will be needed for many of the tests, but there should be enough to test various cases still.
0ba97b3
to
cf7b1c0
Compare
This is one of the simple additions to shuffleToIdentity that help it look through intermediate zext/sext instructions. The other change here is the removal of a check that both operands of the original shuffle are instructions, which is a relic from a previous version.
cf7b1c0
to
20638bc
Compare
…lvm#92696) This is one of the simple additions to shuffleToIdentity that help it look through intermediate zext/sext instructions.
This is one of the simple additions to shuffleToIdentity that help it look through intermediate zext/sext instructions.