Skip to content

[IR] Add nowrap flags for trunc instruction #85592

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

Merged
merged 3 commits into from
Mar 29, 2024
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
8 changes: 8 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11295,6 +11295,9 @@ Syntax:
::

<result> = trunc <ty> <value> to <ty2> ; yields ty2
<result> = trunc nsw <ty> <value> to <ty2> ; yields ty2
<result> = trunc nuw <ty> <value> to <ty2> ; yields ty2
<result> = trunc nuw nsw <ty> <value> to <ty2> ; yields ty2

Overview:
"""""""""
Expand All @@ -11318,6 +11321,11 @@ and converts the remaining bits to ``ty2``. Since the source size must
be larger than the destination size, ``trunc`` cannot be a *no-op cast*.
It will always truncate bits.

If the ``nuw`` keyword is present, and any of the truncated bits are zero,
the result is a :ref:`poison value <poisonvalues>`. If the ``nsw`` keyword
is present, and any of the truncated bits are not the same as the top bit
of the truncation result, the result is a :ref:`poison value <poisonvalues>`.

Example:
""""""""

Expand Down
7 changes: 7 additions & 0 deletions llvm/include/llvm/Bitcode/LLVMBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,13 @@ enum OverflowingBinaryOperatorOptionalFlags {
OBO_NO_SIGNED_WRAP = 1
};

/// TruncInstOptionalFlags - Flags for serializing
/// TruncInstOptionalFlags's SubclassOptionalData contents.
enum TruncInstOptionalFlags {
TIO_NO_UNSIGNED_WRAP = 0,
TIO_NO_SIGNED_WRAP = 1
};

/// FastMath Flags
/// This is a fixed layout derived from the bitcode emitted by LLVM 5.0
/// intended to decouple the in-memory representation from the serialization.
Expand Down
35 changes: 35 additions & 0 deletions llvm/include/llvm/IR/Instructions.h
Original file line number Diff line number Diff line change
Expand Up @@ -5345,6 +5345,8 @@ class TruncInst : public CastInst {
TruncInst *cloneImpl() const;

public:
enum { AnyWrap = 0, NoUnsignedWrap = (1 << 0), NoSignedWrap = (1 << 1) };

/// Constructor with insert-before-instruction semantics
TruncInst(
Value *S, ///< The value to be truncated
Expand Down Expand Up @@ -5376,6 +5378,39 @@ class TruncInst : public CastInst {
static bool classof(const Value *V) {
return isa<Instruction>(V) && classof(cast<Instruction>(V));
}

void setHasNoUnsignedWrap(bool B) {
SubclassOptionalData =
(SubclassOptionalData & ~NoUnsignedWrap) | (B * NoUnsignedWrap);
}
void setHasNoSignedWrap(bool B) {
SubclassOptionalData =
(SubclassOptionalData & ~NoSignedWrap) | (B * NoSignedWrap);
}

/// Test whether this operation is known to never
/// undergo unsigned overflow, aka the nuw property.
bool hasNoUnsignedWrap() const {
return SubclassOptionalData & NoUnsignedWrap;
}

/// Test whether this operation is known to never
/// undergo signed overflow, aka the nsw property.
bool hasNoSignedWrap() const {
return (SubclassOptionalData & NoSignedWrap) != 0;
}

/// Returns the no-wrap kind of the operation.
unsigned getNoWrapKind() const {
unsigned NoWrapKind = 0;
if (hasNoUnsignedWrap())
NoWrapKind |= NoUnsignedWrap;

if (hasNoSignedWrap())
NoWrapKind |= NoSignedWrap;

return NoWrapKind;
}
};

//===----------------------------------------------------------------------===//
Expand Down
14 changes: 13 additions & 1 deletion llvm/lib/AsmParser/LLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6814,7 +6814,19 @@ int LLParser::parseInstruction(Instruction *&Inst, BasicBlock *BB,
Inst->setNonNeg();
return 0;
}
case lltok::kw_trunc:
case lltok::kw_trunc: {
bool NUW = EatIfPresent(lltok::kw_nuw);
bool NSW = EatIfPresent(lltok::kw_nsw);
if (!NUW)
NUW = EatIfPresent(lltok::kw_nuw);
if (parseCast(Inst, PFS, KeywordVal))
return true;
if (NUW)
cast<TruncInst>(Inst)->setHasNoUnsignedWrap(true);
if (NSW)
cast<TruncInst>(Inst)->setHasNoSignedWrap(true);
return false;
}
case lltok::kw_sext:
case lltok::kw_fptrunc:
case lltok::kw_fpext:
Expand Down
16 changes: 13 additions & 3 deletions llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5022,9 +5022,19 @@ 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);

if (OpNum < Record.size()) {
if (Opc == Instruction::ZExt) {
if (Record[OpNum] & (1 << bitc::PNNI_NON_NEG))
cast<PossiblyNonNegInst>(I)->setNonNeg(true);
} else if (Opc == Instruction::Trunc) {
if (Record[OpNum] & (1 << bitc::TIO_NO_UNSIGNED_WRAP))
cast<TruncInst>(I)->setHasNoUnsignedWrap(true);
if (Record[OpNum] & (1 << bitc::TIO_NO_SIGNED_WRAP))
cast<TruncInst>(I)->setHasNoSignedWrap(true);
}
}

InstructionList.push_back(I);
break;
}
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,11 @@ static uint64_t getOptimizationFlags(const Value *V) {
} else if (const auto *NNI = dyn_cast<PossiblyNonNegInst>(V)) {
if (NNI->hasNonNeg())
Flags |= 1 << bitc::PNNI_NON_NEG;
} else if (const auto *TI = dyn_cast<TruncInst>(V)) {
if (TI->hasNoSignedWrap())
Flags |= 1 << bitc::TIO_NO_SIGNED_WRAP;
if (TI->hasNoUnsignedWrap())
Flags |= 1 << bitc::TIO_NO_UNSIGNED_WRAP;
}

return Flags;
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/IR/AsmWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,11 @@ static void WriteOptimizationInfo(raw_ostream &Out, const User *U) {
} else if (const auto *NNI = dyn_cast<PossiblyNonNegInst>(U)) {
if (NNI->hasNonNeg())
Out << " nneg";
} else if (const auto *TI = dyn_cast<TruncInst>(U)) {
if (TI->hasNoUnsignedWrap())
Out << " nuw";
if (TI->hasNoSignedWrap())
Out << " nsw";
}
}

Expand Down
32 changes: 28 additions & 4 deletions llvm/lib/IR/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,17 @@ bool Instruction::isOnlyUserOfAnyOperand() {
}

void Instruction::setHasNoUnsignedWrap(bool b) {
cast<OverflowingBinaryOperator>(this)->setHasNoUnsignedWrap(b);
if (auto *Inst = dyn_cast<OverflowingBinaryOperator>(this))
Inst->setHasNoUnsignedWrap(b);
else
cast<TruncInst>(this)->setHasNoUnsignedWrap(b);
}

void Instruction::setHasNoSignedWrap(bool b) {
cast<OverflowingBinaryOperator>(this)->setHasNoSignedWrap(b);
if (auto *Inst = dyn_cast<OverflowingBinaryOperator>(this))
Inst->setHasNoSignedWrap(b);
else
cast<TruncInst>(this)->setHasNoSignedWrap(b);
}

void Instruction::setIsExact(bool b) {
Expand All @@ -388,11 +394,17 @@ void Instruction::setNonNeg(bool b) {
}

bool Instruction::hasNoUnsignedWrap() const {
return cast<OverflowingBinaryOperator>(this)->hasNoUnsignedWrap();
if (auto *Inst = dyn_cast<OverflowingBinaryOperator>(this))
return Inst->hasNoUnsignedWrap();

return cast<TruncInst>(this)->hasNoUnsignedWrap();
}

bool Instruction::hasNoSignedWrap() const {
return cast<OverflowingBinaryOperator>(this)->hasNoSignedWrap();
if (auto *Inst = dyn_cast<OverflowingBinaryOperator>(this))
return Inst->hasNoSignedWrap();

return cast<TruncInst>(this)->hasNoSignedWrap();
}

bool Instruction::hasNonNeg() const {
Expand Down Expand Up @@ -432,6 +444,11 @@ void Instruction::dropPoisonGeneratingFlags() {
case Instruction::ZExt:
setNonNeg(false);
break;

case Instruction::Trunc:
cast<TruncInst>(this)->setHasNoUnsignedWrap(false);
cast<TruncInst>(this)->setHasNoSignedWrap(false);
break;
}

if (isa<FPMathOperator>(this)) {
Expand Down Expand Up @@ -626,6 +643,13 @@ void Instruction::andIRFlags(const Value *V) {
}
}

if (auto *TI = dyn_cast<TruncInst>(V)) {
if (isa<TruncInst>(this)) {
setHasNoSignedWrap(hasNoSignedWrap() && TI->hasNoSignedWrap());
setHasNoUnsignedWrap(hasNoUnsignedWrap() && TI->hasNoUnsignedWrap());
}
}

if (auto *PE = dyn_cast<PossiblyExactOperator>(V))
if (isa<PossiblyExactOperator>(this))
setIsExact(isExact() && PE->isExact());
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 @@ -27,6 +27,10 @@ bool Operator::hasPoisonGeneratingFlags() const {
auto *OBO = cast<OverflowingBinaryOperator>(this);
return OBO->hasNoUnsignedWrap() || OBO->hasNoSignedWrap();
}
case Instruction::Trunc: {
auto *TI = dyn_cast<TruncInst>(this);
return TI->hasNoUnsignedWrap() || TI->hasNoSignedWrap();
Copy link
Member

Choose a reason for hiding this comment

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

TI may be null because this may be a CastConstantExpr.

Copy link
Member Author

Choose a reason for hiding this comment

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

@dtcxzyw I will open another pull and fix this.

Copy link
Member Author

@elhewaty elhewaty Mar 30, 2024

Choose a reason for hiding this comment

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

Can you think of a small test for this?

define i32 @unittest_data_add() {
entry:
  %add = add i32 trunc (i64 sub (i64 0, i64 ptrtoint (ptr @__dtbo_testcases_begin to i64)) to i32), 1
  %conv = sext i32 %add to i64
  %call1 = call ptr @kmalloc(i64 %conv, ptr null) ; I tried to replace this with call i32 @use(i64 %conv) and remove all the other functions, but It works fine 
  ret i32 0
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@g_var = external global [0 x i8]

define i64 @test_ret_noundef_add_constantexpr() {
entry:
  %add = add i32 trunc (i64 sub (i64 0, i64 ptrtoint (ptr @g_var to i64)) to i32), 1
  %conv = sext i32 %add to i64
  ret i64 %conv
}

Think you can put it in test/Transforms/FunctionAttrs/noundef.ll.

}
case Instruction::UDiv:
case Instruction::SDiv:
case Instruction::AShr:
Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ PoisonFlags::PoisonFlags(const Instruction *I) {
Disjoint = PDI->isDisjoint();
if (auto *PNI = dyn_cast<PossiblyNonNegInst>(I))
NNeg = PNI->hasNonNeg();
if (auto *TI = dyn_cast<TruncInst>(I)) {
NUW = TI->hasNoUnsignedWrap();
NSW = TI->hasNoSignedWrap();
}
}

void PoisonFlags::apply(Instruction *I) {
Expand All @@ -72,6 +76,10 @@ void PoisonFlags::apply(Instruction *I) {
PDI->setIsDisjoint(Disjoint);
if (auto *PNI = dyn_cast<PossiblyNonNegInst>(I))
PNI->setNonNeg(NNeg);
if (isa<TruncInst>(I)) {
I->setHasNoUnsignedWrap(NUW);
I->setHasNoSignedWrap(NSW);
}
}

/// ReuseOrCreateCast - Arrange for there to be a cast of V to Ty at IP,
Expand Down
48 changes: 48 additions & 0 deletions llvm/test/Assembler/flags.ll
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,51 @@ define i64 @test_or(i64 %a, i64 %b) {
%res = or disjoint i64 %a, %b
ret i64 %res
}

define i32 @test_trunc_signed(i64 %a) {
; CHECK: %res = trunc nsw i64 %a to i32
%res = trunc nsw i64 %a to i32
ret i32 %res
}

define i32 @test_trunc_unsigned(i64 %a) {
; CHECK: %res = trunc nuw i64 %a to i32
%res = trunc nuw i64 %a to i32
ret i32 %res
}

define i32 @test_trunc_both(i64 %a) {
; CHECK: %res = trunc nuw nsw i64 %a to i32
%res = trunc nuw nsw i64 %a to i32
ret i32 %res
}

define i32 @test_trunc_both_reversed(i64 %a) {
; CHECK: %res = trunc nuw nsw i64 %a to i32
%res = trunc nsw nuw i64 %a to i32
ret i32 %res
}

define <2 x i32> @test_trunc_signed_vector(<2 x i64> %a) {
; CHECK: %res = trunc nsw <2 x i64> %a to <2 x i32>
%res = trunc nsw <2 x i64> %a to <2 x i32>
ret <2 x i32> %res
}

define <2 x i32> @test_trunc_unsigned_vector(<2 x i64> %a) {
; CHECK: %res = trunc nuw <2 x i64> %a to <2 x i32>
%res = trunc nuw <2 x i64> %a to <2 x i32>
ret <2 x i32> %res
}

define <2 x i32> @test_trunc_both_vector(<2 x i64> %a) {
; CHECK: %res = trunc nuw nsw <2 x i64> %a to <2 x i32>
%res = trunc nuw nsw <2 x i64> %a to <2 x i32>
ret <2 x i32> %res
}

define <2 x i32> @test_trunc_both_reversed_vector(<2 x i64> %a) {
; CHECK: %res = trunc nuw nsw <2 x i64> %a to <2 x i32>
%res = trunc nsw nuw <2 x i64> %a to <2 x i32>
ret <2 x i32> %res
}
29 changes: 23 additions & 6 deletions llvm/test/Bitcode/flags.ll
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,34 @@ second: ; preds = %first
%ll = zext i32 %s to i64
%jj = or disjoint i32 %a, 0
%oo = or i32 %a, 0
%tu = trunc nuw i32 %a to i16
%ts = trunc nsw i32 %a to i16
%tus = trunc nuw nsw i32 %a to i16
%t = trunc i32 %a to i16
%tuv = trunc nuw <2 x i32> %aa to <2 x i16>
%tsv = trunc nsw <2 x i32> %aa to <2 x i16>
%tusv = trunc nuw nsw <2 x i32> %aa to <2 x i16>
%tv = trunc <2 x i32> %aa to <2 x i16>
unreachable

first: ; preds = %entry
%a = bitcast i32 0 to i32 ; <i32> [#uses=8]
%uu = add nuw i32 %a, 0 ; <i32> [#uses=0]
%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]
first: ; preds = %entry
%aa = bitcast <2 x i32> <i32 0, i32 0> to <2 x i32>
%a = bitcast i32 0 to i32 ; <i32> [#uses=8]
%uu = add nuw i32 %a, 0 ; <i32> [#uses=0]
%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
%mm = or disjoint i32 %a, 0
%nn = or i32 %a, 0
%tuu = trunc nuw i32 %a to i16
%tss = trunc nsw i32 %a to i16
%tuss = trunc nuw nsw i32 %a to i16
%tt = trunc i32 %a to i16
%ttuv = trunc nuw <2 x i32> %aa to <2 x i16>
%ttsv = trunc nsw <2 x i32> %aa to <2 x i16>
%ttusv = trunc nuw nsw <2 x i32> %aa to <2 x i16>
%ttv = trunc <2 x i32> %aa to <2 x i16>
br label %second
}
19 changes: 15 additions & 4 deletions llvm/test/Transforms/InstCombine/freeze.ll
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ exit:

define ptr @freeze_load_noundef(ptr %ptr) {
; CHECK-LABEL: @freeze_load_noundef(
; CHECK-NEXT: [[P:%.*]] = load ptr, ptr [[PTR:%.*]], align 8, !noundef !0
; CHECK-NEXT: [[P:%.*]] = load ptr, ptr [[PTR:%.*]], align 8, !noundef [[META0:![0-9]+]]
; CHECK-NEXT: ret ptr [[P]]
;
%p = load ptr, ptr %ptr, !noundef !0
Expand All @@ -1059,7 +1059,7 @@ define ptr @freeze_load_noundef(ptr %ptr) {

define ptr @freeze_load_dereferenceable(ptr %ptr) {
; CHECK-LABEL: @freeze_load_dereferenceable(
; CHECK-NEXT: [[P:%.*]] = load ptr, ptr [[PTR:%.*]], align 8, !dereferenceable !1
Copy link
Contributor

Choose a reason for hiding this comment

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

This and above seem unrelated.

; CHECK-NEXT: [[P:%.*]] = load ptr, ptr [[PTR:%.*]], align 8, !dereferenceable [[META1:![0-9]+]]
; CHECK-NEXT: ret ptr [[P]]
;
%p = load ptr, ptr %ptr, !dereferenceable !1
Expand Down Expand Up @@ -1138,15 +1138,26 @@ define i32 @propagate_drop_flags_or(i32 %arg) {
ret i32 %v1.fr
}

define i32 @propagate_drop_flags_trunc(i64 %arg) {
; CHECK-LABEL: @propagate_drop_flags_trunc(
; CHECK-NEXT: [[ARG_FR:%.*]] = freeze i64 [[ARG:%.*]]
; CHECK-NEXT: [[V1:%.*]] = trunc i64 [[ARG_FR]] to i32
; CHECK-NEXT: ret i32 [[V1]]
;
%v1 = trunc nsw nuw i64 %arg to i32
%v1.fr = freeze i32 %v1
ret i32 %v1.fr
}

!0 = !{}
!1 = !{i64 4}
!2 = !{i32 0, i32 100}
;.
; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
; CHECK: attributes #[[ATTR1]] = { nounwind }
;.
; CHECK: [[META0:![0-9]+]] = !{}
; CHECK: [[META1:![0-9]+]] = !{i64 4}
; CHECK: [[META0]] = !{}
; CHECK: [[META1]] = !{i64 4}
; CHECK: [[RNG2]] = !{i32 0, i32 100}
; CHECK: [[RNG3]] = !{i32 0, i32 33}
;.
Loading