Skip to content
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

[IR] Add zext nneg flag #67982

Merged
merged 3 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11210,6 +11210,10 @@ Overview:

The '``zext``' instruction zero extends its operand to type ``ty2``.

The ``nneg`` (non-negative) flag, if present, specifies that the operand is
non-negative. This property may be used by optimization passes to later
convert the ``zext`` into a ``sext``.

Arguments:
""""""""""

Expand All @@ -11226,6 +11230,9 @@ until it reaches the size of the destination type, ``ty2``.

When zero extending from i1, the result will always be either 0 or 1.

If the ``nneg`` flag is set, and the ``zext`` argument is negative, the result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description of nneg is very minimal, vs the more detailed description in the RFC and PR. Maybe add to "Overview" something like "The nneg (non-negative) flag, if present, specifies that the operand is non-negative. This property may be used by optimization passes to later convert the zext to a semantically equivalent sext."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! (I dropped the "semantically equivalent" bit because it's a refinement, not an equivalence.)

is a poison value.

Example:
""""""""

Expand All @@ -11235,6 +11242,9 @@ Example:
%Y = zext i1 true to i32 ; yields i32:1
%Z = zext <2 x i16> <i16 8, i16 7> to <2 x i32> ; yields <i32 8, i32 7>

%a = zext nneg i8 127 to i16 ; yields i16 127
%b = zext nneg i8 -1 to i16 ; yields i16 poison

.. _i_sext:

'``sext .. to``' Instruction
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/AsmParser/LLToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ enum Kind {
kw_nsw,
kw_exact,
kw_inbounds,
kw_nneg,
kw_inrange,
kw_addrspace,
kw_section,
Expand Down
3 changes: 3 additions & 0 deletions llvm/include/llvm/Bitcode/LLVMBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@ enum FastMathMap {
AllowReassoc = (1 << 7)
};

/// Flags for serializing PossiblyNonNegInst's SubclassOptionalData contents.
enum PossiblyNonNegInstOptionalFlags { PNNI_NON_NEG = 0 };

/// PossiblyExactOperatorOptionalFlags - Flags for serializing
/// PossiblyExactOperator's SubclassOptionalData contents.
enum PossiblyExactOperatorOptionalFlags { PEO_EXACT = 0 };
Expand Down
14 changes: 14 additions & 0 deletions llvm/include/llvm/IR/InstrTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,20 @@ class CastInst : public UnaryInstruction {
}
};

/// Instruction that can have a nneg flag (only zext).
class PossiblyNonNegInst : public CastInst {
public:
enum { NonNeg = (1 << 0) };

static bool classof(const Instruction *I) {
return I->getOpcode() == Instruction::ZExt;
}

static bool classof(const Value *V) {
return isa<Instruction>(V) && classof(cast<Instruction>(V));
}
};

//===----------------------------------------------------------------------===//
// CmpInst Class
//===----------------------------------------------------------------------===//
Expand Down
7 changes: 7 additions & 0 deletions llvm/include/llvm/IR/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,19 @@ class Instruction : public User,
/// which supports this flag. See LangRef.html for the meaning of this flag.
void setIsExact(bool b = true);

/// Set or clear the nneg flag on this instruction, which must be a zext
/// instruction.
void setNonNeg(bool b = true);

/// Determine whether the no unsigned wrap flag is set.
bool hasNoUnsignedWrap() const LLVM_READONLY;

/// Determine whether the no signed wrap flag is set.
bool hasNoSignedWrap() const LLVM_READONLY;

/// Determine whether the the nneg flag is set.
bool hasNonNeg() const LLVM_READONLY;

/// Return true if this operator has flags which may cause this instruction
/// to evaluate to poison despite having non-poison inputs.
bool hasPoisonGeneratingFlags() const LLVM_READONLY;
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/AsmParser/LLLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(nsw);
KEYWORD(exact);
KEYWORD(inbounds);
KEYWORD(nneg);
KEYWORD(inrange);
KEYWORD(addrspace);
KEYWORD(section);
Expand Down
10 changes: 9 additions & 1 deletion llvm/lib/AsmParser/LLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6383,8 +6383,16 @@ int LLParser::parseInstruction(Instruction *&Inst, BasicBlock *BB,
}

// Casts.
case lltok::kw_zext: {
bool NonNeg = EatIfPresent(lltok::kw_nneg);
bool Res = parseCast(Inst, PFS, KeywordVal);
if (Res != 0)
return Res;
if (NonNeg)
Inst->setNonNeg();
return 0;
nikic marked this conversation as resolved.
Show resolved Hide resolved
}
case lltok::kw_trunc:
case lltok::kw_zext:
case lltok::kw_sext:
case lltok::kw_fptrunc:
case lltok::kw_fpext:
Expand Down
10 changes: 7 additions & 3 deletions llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4875,12 +4875,13 @@ Error BitcodeReader::parseFunctionBody(Function *F) {
Value *Op;
unsigned OpTypeID;
if (getValueTypePair(Record, OpNum, NextValueNo, Op, OpTypeID, CurBB) ||
OpNum+2 != Record.size())
OpNum + 1 > Record.size())
return error("Invalid record");

ResTypeID = Record[OpNum];
ResTypeID = Record[OpNum++];
Type *ResTy = getTypeByID(ResTypeID);
int Opc = getDecodedCastOpcode(Record[OpNum + 1]);
int Opc = getDecodedCastOpcode(Record[OpNum++]);

if (Opc == -1 || !ResTy)
return error("Invalid record");
Instruction *Temp = nullptr;
Expand All @@ -4896,6 +4897,9 @@ Error BitcodeReader::parseFunctionBody(Function *F) {
return error("Invalid cast");
I = CastInst::Create(CastOp, Op, ResTy);
}
if (OpNum < Record.size() && isa<PossiblyNonNegInst>(I) &&
(Record[OpNum] & (1 << bitc::PNNI_NON_NEG)))
I->setNonNeg(true);
InstructionList.push_back(I);
break;
}
Expand Down
22 changes: 22 additions & 0 deletions llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ enum {
FUNCTION_INST_BINOP_ABBREV,
FUNCTION_INST_BINOP_FLAGS_ABBREV,
FUNCTION_INST_CAST_ABBREV,
FUNCTION_INST_CAST_FLAGS_ABBREV,
FUNCTION_INST_RET_VOID_ABBREV,
FUNCTION_INST_RET_VAL_ABBREV,
FUNCTION_INST_UNREACHABLE_ABBREV,
Expand Down Expand Up @@ -1549,6 +1550,9 @@ static uint64_t getOptimizationFlags(const Value *V) {
Flags |= bitc::AllowContract;
if (FPMO->hasApproxFunc())
Flags |= bitc::ApproxFunc;
} else if (const auto *NNI = dyn_cast<PossiblyNonNegInst>(V)) {
if (NNI->hasNonNeg())
Flags |= 1 << bitc::PNNI_NON_NEG;
}

return Flags;
Expand Down Expand Up @@ -2825,6 +2829,12 @@ void ModuleBitcodeWriter::writeInstruction(const Instruction &I,
AbbrevToUse = FUNCTION_INST_CAST_ABBREV;
Vals.push_back(VE.getTypeID(I.getType()));
Vals.push_back(getEncodedCastOpcode(I.getOpcode()));
uint64_t Flags = getOptimizationFlags(&I);
if (Flags != 0) {
if (AbbrevToUse == FUNCTION_INST_CAST_ABBREV)
AbbrevToUse = FUNCTION_INST_CAST_FLAGS_ABBREV;
Vals.push_back(Flags);
}
} else {
assert(isa<BinaryOperator>(I) && "Unknown instruction!");
Code = bitc::FUNC_CODE_INST_BINOP;
Expand Down Expand Up @@ -3646,6 +3656,18 @@ void ModuleBitcodeWriter::writeBlockInfo() {
FUNCTION_INST_CAST_ABBREV)
llvm_unreachable("Unexpected abbrev ordering!");
}
{ // INST_CAST_FLAGS abbrev for FUNCTION_BLOCK.
auto Abbv = std::make_shared<BitCodeAbbrev>();
Abbv->Add(BitCodeAbbrevOp(bitc::FUNC_CODE_INST_CAST));
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // OpVal
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, // dest ty
VE.computeBitsRequiredForTypeIndicies()));
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 4)); // opc
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 8)); // flags
if (Stream.EmitBlockInfoAbbrev(bitc::FUNCTION_BLOCK_ID, Abbv) !=
FUNCTION_INST_CAST_FLAGS_ABBREV)
llvm_unreachable("Unexpected abbrev ordering!");
}

{ // INST_RET abbrev for FUNCTION_BLOCK.
auto Abbv = std::make_shared<BitCodeAbbrev>();
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/IR/AsmWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,9 @@ static void WriteOptimizationInfo(raw_ostream &Out, const User *U) {
} else if (const GEPOperator *GEP = dyn_cast<GEPOperator>(U)) {
if (GEP->isInBounds())
Out << " inbounds";
} else if (const auto *NNI = dyn_cast<PossiblyNonNegInst>(U)) {
if (NNI->hasNonNeg())
Out << " nneg";
}
}

Expand Down
24 changes: 24 additions & 0 deletions llvm/lib/IR/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ void Instruction::setIsExact(bool b) {
cast<PossiblyExactOperator>(this)->setIsExact(b);
}

void Instruction::setNonNeg(bool b) {
assert(isa<PossiblyNonNegInst>(this) && "Must be zext");
SubclassOptionalData = (SubclassOptionalData & ~PossiblyNonNegInst::NonNeg) |
(b * PossiblyNonNegInst::NonNeg);
}

bool Instruction::hasNoUnsignedWrap() const {
return cast<OverflowingBinaryOperator>(this)->hasNoUnsignedWrap();
}
Expand All @@ -179,6 +185,11 @@ bool Instruction::hasNoSignedWrap() const {
return cast<OverflowingBinaryOperator>(this)->hasNoSignedWrap();
}

bool Instruction::hasNonNeg() const {
assert(isa<PossiblyNonNegInst>(this) && "Must be zext");
return (SubclassOptionalData & PossiblyNonNegInst::NonNeg) != 0;
}

bool Instruction::hasPoisonGeneratingFlags() const {
return cast<Operator>(this)->hasPoisonGeneratingFlags();
}
Expand All @@ -203,7 +214,12 @@ void Instruction::dropPoisonGeneratingFlags() {
case Instruction::GetElementPtr:
cast<GetElementPtrInst>(this)->setIsInBounds(false);
break;

case Instruction::ZExt:
setNonNeg(false);
break;
}

if (isa<FPMathOperator>(this)) {
setHasNoNaNs(false);
setHasNoInfs(false);
Expand Down Expand Up @@ -378,6 +394,10 @@ void Instruction::copyIRFlags(const Value *V, bool IncludeWrapFlags) {
if (auto *SrcGEP = dyn_cast<GetElementPtrInst>(V))
if (auto *DestGEP = dyn_cast<GetElementPtrInst>(this))
DestGEP->setIsInBounds(SrcGEP->isInBounds() || DestGEP->isInBounds());

if (auto *NNI = dyn_cast<PossiblyNonNegInst>(V))
if (isa<PossiblyNonNegInst>(this))
setNonNeg(NNI->hasNonNeg());
}

void Instruction::andIRFlags(const Value *V) {
Expand All @@ -403,6 +423,10 @@ void Instruction::andIRFlags(const Value *V) {
if (auto *SrcGEP = dyn_cast<GetElementPtrInst>(V))
if (auto *DestGEP = dyn_cast<GetElementPtrInst>(this))
DestGEP->setIsInBounds(SrcGEP->isInBounds() && DestGEP->isInBounds());

if (auto *NNI = dyn_cast<PossiblyNonNegInst>(V))
if (isa<PossiblyNonNegInst>(this))
setNonNeg(hasNonNeg() && NNI->hasNonNeg());
}

const char *Instruction::getOpcodeName(unsigned OpCode) {
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/IR/Operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ bool Operator::hasPoisonGeneratingFlags() const {
// Note: inrange exists on constexpr only
return GEP->isInBounds() || GEP->getInRangeIndex() != std::nullopt;
}
case Instruction::ZExt:
if (auto *NNI = dyn_cast<PossiblyNonNegInst>(this))
Copy link
Collaborator

@topperc topperc Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a dyn_cast instead of a cast like the other cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in case it's a constant expression. I haven't completely finished the zext expr removal yet.

return NNI->hasNonNeg();
return false;
default:
if (const auto *FP = dyn_cast<FPMathOperator>(this))
return FP->hasNoNaNs() || FP->hasNoInfs();
Expand Down
6 changes: 6 additions & 0 deletions llvm/test/Assembler/flags.ll
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,9 @@ define i64 @mul_unsigned_ce() {
ret i64 mul nuw (i64 ptrtoint (ptr @addr to i64), i64 91)
}

define i64 @test_zext(i32 %a) {
; CHECK: %res = zext nneg i32 %a to i64
%res = zext nneg i32 %a to i64
ret i64 %res
}

4 changes: 4 additions & 0 deletions llvm/test/Bitcode/flags.ll
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ second: ; preds = %first
%s = add nsw i32 %a, 0 ; <i32> [#uses=0]
%us = add nuw nsw i32 %a, 0 ; <i32> [#uses=0]
%z = add i32 %a, 0 ; <i32> [#uses=0]
%hh = zext nneg i32 %a to i64
%ll = zext i32 %s to i64
unreachable

first: ; preds = %entry
Expand All @@ -24,5 +26,7 @@ first: ; preds = %entry
%ss = add nsw i32 %a, 0 ; <i32> [#uses=0]
%uuss = add nuw nsw i32 %a, 0 ; <i32> [#uses=0]
%zz = add i32 %a, 0 ; <i32> [#uses=0]
%kk = zext nneg i32 %a to i64
%rr = zext i32 %ss to i64
br label %second
}
11 changes: 11 additions & 0 deletions llvm/test/Transforms/InstCombine/freeze.ll
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,17 @@ define i32 @freeze_ctpop(i32 %x) {
ret i32 %fr
}

define i32 @freeze_zext_nneg(i8 %x) {
; CHECK-LABEL: @freeze_zext_nneg(
; CHECK-NEXT: [[X_FR:%.*]] = freeze i8 [[X:%.*]]
; CHECK-NEXT: [[ZEXT:%.*]] = zext i8 [[X_FR]] to i32
; CHECK-NEXT: ret i32 [[ZEXT]]
;
%zext = zext nneg i8 %x to i32
%fr = freeze i32 %zext
ret i32 %fr
}

!0 = !{}
!1 = !{i64 4}
!2 = !{i32 0, i32 100}
Expand Down
30 changes: 30 additions & 0 deletions llvm/test/Transforms/SimplifyCFG/HoistCode.ll
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,33 @@ end:
%cond = phi fast float [ 0.0, %bb0 ], [ %x, %bb1 ], [ %x, %bb2 ]
ret float %cond
}

define i32 @hoist_zext_flags_preserve(i1 %C, i8 %x) {
; CHECK-LABEL: @hoist_zext_flags_preserve(
; CHECK-NEXT: common.ret:
; CHECK-NEXT: [[Z1:%.*]] = zext nneg i8 [[X:%.*]] to i32
; CHECK-NEXT: ret i32 [[Z1]]
;
br i1 %C, label %T, label %F
T:
%z1 = zext nneg i8 %x to i32
ret i32 %z1
F:
%z2 = zext nneg i8 %x to i32
ret i32 %z2
}

define i32 @hoist_zext_flags_drop(i1 %C, i8 %x) {
; CHECK-LABEL: @hoist_zext_flags_drop(
; CHECK-NEXT: common.ret:
; CHECK-NEXT: [[Z1:%.*]] = zext i8 [[X:%.*]] to i32
; CHECK-NEXT: ret i32 [[Z1]]
;
br i1 %C, label %T, label %F
T:
%z1 = zext nneg i8 %x to i32
ret i32 %z1
F:
%z2 = zext i8 %x to i32
ret i32 %z2
}