diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 2035091be5a684..b4c1671c46a77b 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -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: """""""""" @@ -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 +is a poison value. + Example: """""""" @@ -11235,6 +11242,9 @@ Example: %Y = zext i1 true to i32 ; yields i32:1 %Z = zext <2 x i16> to <2 x i32> ; yields + %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 diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h index 2d6b8a19401d78..773a1b84ea5330 100644 --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -110,6 +110,7 @@ enum Kind { kw_nsw, kw_exact, kw_inbounds, + kw_nneg, kw_inrange, kw_addrspace, kw_section, diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 52e76356a892e4..301d87ee8cee84 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -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 }; diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h index 6095b0a1be69cb..fc5e228168a058 100644 --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -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(V) && classof(cast(V)); + } +}; + //===----------------------------------------------------------------------===// // CmpInst Class //===----------------------------------------------------------------------===// diff --git a/llvm/include/llvm/IR/Instruction.h b/llvm/include/llvm/IR/Instruction.h index af7aa791cb6da6..5142847fa75fff 100644 --- a/llvm/include/llvm/IR/Instruction.h +++ b/llvm/include/llvm/IR/Instruction.h @@ -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; diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 1402c152bb5c31..80b3e6a93727e7 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -565,6 +565,7 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(nsw); KEYWORD(exact); KEYWORD(inbounds); + KEYWORD(nneg); KEYWORD(inrange); KEYWORD(addrspace); KEYWORD(section); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index e104f8b3d1fdba..42f306a99d5eef 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -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; + } case lltok::kw_trunc: - case lltok::kw_zext: case lltok::kw_sext: case lltok::kw_fptrunc: case lltok::kw_fpext: diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 1d1ec988a93d84..23ae135f59b204 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -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; @@ -4896,6 +4897,9 @@ Error BitcodeReader::parseFunctionBody(Function *F) { return error("Invalid cast"); I = CastInst::Create(CastOp, Op, ResTy); } + if (OpNum < Record.size() && isa(I) && + (Record[OpNum] & (1 << bitc::PNNI_NON_NEG))) + I->setNonNeg(true); InstructionList.push_back(I); break; } diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index e991d055f33474..3ce0be66e736c2 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -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, @@ -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(V)) { + if (NNI->hasNonNeg()) + Flags |= 1 << bitc::PNNI_NON_NEG; } return Flags; @@ -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(I) && "Unknown instruction!"); Code = bitc::FUNC_CODE_INST_BINOP; @@ -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(); + 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(); diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp index bd8b3e9ad52215..c738b50a7c721e 100644 --- a/llvm/lib/IR/AsmWriter.cpp +++ b/llvm/lib/IR/AsmWriter.cpp @@ -1348,6 +1348,9 @@ static void WriteOptimizationInfo(raw_ostream &Out, const User *U) { } else if (const GEPOperator *GEP = dyn_cast(U)) { if (GEP->isInBounds()) Out << " inbounds"; + } else if (const auto *NNI = dyn_cast(U)) { + if (NNI->hasNonNeg()) + Out << " nneg"; } } diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp index 9b176eb78888e7..1b3c03348f41a7 100644 --- a/llvm/lib/IR/Instruction.cpp +++ b/llvm/lib/IR/Instruction.cpp @@ -171,6 +171,12 @@ void Instruction::setIsExact(bool b) { cast(this)->setIsExact(b); } +void Instruction::setNonNeg(bool b) { + assert(isa(this) && "Must be zext"); + SubclassOptionalData = (SubclassOptionalData & ~PossiblyNonNegInst::NonNeg) | + (b * PossiblyNonNegInst::NonNeg); +} + bool Instruction::hasNoUnsignedWrap() const { return cast(this)->hasNoUnsignedWrap(); } @@ -179,6 +185,11 @@ bool Instruction::hasNoSignedWrap() const { return cast(this)->hasNoSignedWrap(); } +bool Instruction::hasNonNeg() const { + assert(isa(this) && "Must be zext"); + return (SubclassOptionalData & PossiblyNonNegInst::NonNeg) != 0; +} + bool Instruction::hasPoisonGeneratingFlags() const { return cast(this)->hasPoisonGeneratingFlags(); } @@ -203,7 +214,12 @@ void Instruction::dropPoisonGeneratingFlags() { case Instruction::GetElementPtr: cast(this)->setIsInBounds(false); break; + + case Instruction::ZExt: + setNonNeg(false); + break; } + if (isa(this)) { setHasNoNaNs(false); setHasNoInfs(false); @@ -378,6 +394,10 @@ void Instruction::copyIRFlags(const Value *V, bool IncludeWrapFlags) { if (auto *SrcGEP = dyn_cast(V)) if (auto *DestGEP = dyn_cast(this)) DestGEP->setIsInBounds(SrcGEP->isInBounds() || DestGEP->isInBounds()); + + if (auto *NNI = dyn_cast(V)) + if (isa(this)) + setNonNeg(NNI->hasNonNeg()); } void Instruction::andIRFlags(const Value *V) { @@ -403,6 +423,10 @@ void Instruction::andIRFlags(const Value *V) { if (auto *SrcGEP = dyn_cast(V)) if (auto *DestGEP = dyn_cast(this)) DestGEP->setIsInBounds(SrcGEP->isInBounds() && DestGEP->isInBounds()); + + if (auto *NNI = dyn_cast(V)) + if (isa(this)) + setNonNeg(hasNonNeg() && NNI->hasNonNeg()); } const char *Instruction::getOpcodeName(unsigned OpCode) { diff --git a/llvm/lib/IR/Operator.cpp b/llvm/lib/IR/Operator.cpp index d2a1f2eb49dafe..0c917ad77e1580 100644 --- a/llvm/lib/IR/Operator.cpp +++ b/llvm/lib/IR/Operator.cpp @@ -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(this)) + return NNI->hasNonNeg(); + return false; default: if (const auto *FP = dyn_cast(this)) return FP->hasNoNaNs() || FP->hasNoInfs(); diff --git a/llvm/test/Assembler/flags.ll b/llvm/test/Assembler/flags.ll index 3b54b06b81d4e2..8331edf52a1699 100644 --- a/llvm/test/Assembler/flags.ll +++ b/llvm/test/Assembler/flags.ll @@ -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 +} + diff --git a/llvm/test/Bitcode/flags.ll b/llvm/test/Bitcode/flags.ll index 6febaa6b40df86..a6e368b7e76327 100644 --- a/llvm/test/Bitcode/flags.ll +++ b/llvm/test/Bitcode/flags.ll @@ -16,6 +16,8 @@ second: ; preds = %first %s = add nsw i32 %a, 0 ; [#uses=0] %us = add nuw nsw i32 %a, 0 ; [#uses=0] %z = add i32 %a, 0 ; [#uses=0] + %hh = zext nneg i32 %a to i64 + %ll = zext i32 %s to i64 unreachable first: ; preds = %entry @@ -24,5 +26,7 @@ first: ; preds = %entry %ss = add nsw i32 %a, 0 ; [#uses=0] %uuss = add nuw nsw i32 %a, 0 ; [#uses=0] %zz = add i32 %a, 0 ; [#uses=0] + %kk = zext nneg i32 %a to i64 + %rr = zext i32 %ss to i64 br label %second } diff --git a/llvm/test/Transforms/InstCombine/freeze.ll b/llvm/test/Transforms/InstCombine/freeze.ll index f8e9a757e2bc93..3fde49d0848127 100644 --- a/llvm/test/Transforms/InstCombine/freeze.ll +++ b/llvm/test/Transforms/InstCombine/freeze.ll @@ -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} diff --git a/llvm/test/Transforms/SimplifyCFG/HoistCode.ll b/llvm/test/Transforms/SimplifyCFG/HoistCode.ll index 4088ecfc818982..08cf6cd5be80cf 100644 --- a/llvm/test/Transforms/SimplifyCFG/HoistCode.ll +++ b/llvm/test/Transforms/SimplifyCFG/HoistCode.ll @@ -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 +}