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

Add the 'initializes' attribute langref and support #84803

Open
wants to merge 32 commits into
base: main
Choose a base branch
from

Conversation

haopliu
Copy link
Contributor

@haopliu haopliu commented Mar 11, 2024

We propose adding a new LLVM attribute, initializes((Lo1,Hi1),(Lo2,Hi2),...), which expresses the notion of memory space (i.e., intervals, in bytes) that the argument pointing to is initialized in the function.

Will commit the attribute inferring in the follow-up PRs.

https://discourse.llvm.org/t/rfc-llvm-new-initialized-parameter-attribute-for-improved-interprocedural-dse/77337

@haopliu haopliu marked this pull request as ready for review March 11, 2024 21:23
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 11, 2024

@llvm/pr-subscribers-llvm-adt
@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-ir

Author: Haopeng Liu (haopliu)

Changes

We propose adding a new LLVM attribute, initialized((Lo1,Hi1),(Lo2,Hi2),...), which expresses the notion of memory space (i.e., intervals, in bytes) that the argument pointing to is initialized in the function.

Will commit the attribute inferring in the follow-up PRs.

https://discourse.llvm.org/t/rfc-llvm-new-initialized-parameter-attribute-for-improved-interprocedural-dse/77337


Full diff: https://github.com/llvm/llvm-project/pull/84803.diff

1 Files Affected:

  • (modified) llvm/docs/LangRef.rst (+22)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index b70220dec92615..39a555edfa80d6 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1621,6 +1621,28 @@ Currently, only the following parameter attributes are defined:
     ``readonly`` or a ``memory`` attribute that does not contain
     ``argmem: write``.
 
+``initialized((Lo1,Hi1),...)``
+    This attribute is a list of const ranges in ascending order with no
+    overlapping or continuous. It indicates that the function initializes the
+    memory through the pointer argument, [%p+LoN, %p+HiN): there are no reads,
+    and no special accesses (such as volatile access or untrackable capture)
+    before the initialization in the function. LoN/HiN are 64-bit ints;
+    negative values are allowed in case a pointer to partway through the
+    allocation is passed to.
+
+    This attribute implies that the function initializes and does not read
+    before initialization through this pointer argument, even though it may
+    read the memory before initialization that the pointer points to, such
+    as through other arguments.
+
+    The ``writable`` or ``dereferenceable`` attribute does not imply
+    ``initialized`` attribute, and ``initialized`` does not imply ``writeonly``
+    since cases that read from the pointer after write, can be ``initialized``
+    but not ``writeonly``.
+
+    Note that this attribute does not apply to the unwind edge: the memory may
+    not actually be written to when unwinding happens.
+
 ``dead_on_unwind``
     At a high level, this attribute indicates that the pointer argument is dead
     if the call unwinds, in the sense that the caller will not depend on the

@@ -1621,6 +1621,28 @@ Currently, only the following parameter attributes are defined:
``readonly`` or a ``memory`` attribute that does not contain
``argmem: write``.

``initialized((Lo1,Hi1),...)``
This attribute is a list of const ranges in ascending order with no
overlapping or continuous. It indicates that the function initializes the
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "with no overlapping or adjoining list elements"? or something like that (felt like it was missing a word at the end at least)

Copy link
Contributor

Choose a reason for hiding this comment

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

We should start this off with a one sentence overview of the attribute. So starting with something like "This indicates that the function initializes the ranges of the pointer parameter's memory." Then describe what "initialize" means. Then the random details like non-overlapping/continuous ranges at the end.

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!

and no special accesses (such as volatile access or untrackable capture)
before the initialization in the function. LoN/HiN are 64-bit ints;
negative values are allowed in case a pointer to partway through the
allocation is passed to.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "in case the argument points partway into an allocation." ?

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!

as through other arguments.

The ``writable`` or ``dereferenceable`` attribute does not imply
``initialized`` attribute, and ``initialized`` does not imply ``writeonly``
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "does not imply [the] initialized attribute, and ... writeonly, since initialized allows reading from the pointer after writing." ?

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!

@aeubanks
Copy link
Contributor

we should combine this PR with the PR that adds support for this in LLVM, or else it's weird if we're documenting something that LLVM doesn't support yet

since cases that read from the pointer after write, can be ``initialized``
but not ``writeonly``.

Note that this attribute does not apply to the unwind edge: the memory may
Copy link
Contributor

Choose a reason for hiding this comment

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

as mentioned in the RFC, we should be more accurate here. the part of the attribute where the memory is read from before a write still must apply on the unwind edge. Also, I'd move this up since this is important to the semantics

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry, I meant that the memory is not read from before a write

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!

@aeubanks
Copy link
Contributor

unintentional add of FunctionAttrs.cpp change?

we should combine this PR with the PR that adds support for this in LLVM, or else it's weird if we're documenting something that LLVM doesn't support yet

specifically just the attribute support (e.g. bitcode) not any of the inference or usage of it

llvm/docs/LangRef.rst Show resolved Hide resolved
special accesses (such as volatile access or untrackable capture) before
the initialization write in the function.

This attribute implies that the function initializes and does not read
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't use implies.

`This attribute only holds for the memory accessed via this pointer parameter. Other arbitrary accesses to the same memory via other pointers are allowed.

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!

written to when unwinding happens.

The ``writable`` or ``dereferenceable`` attribute does not imply the
``initialized`` attribute, and ``initialized`` does not imply ``writeonly``
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd separate this into two sentences.

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!

``initialized`` attribute, and ``initialized`` does not imply ``writeonly``
since ``initialized`` allows reading from the pointer after writing.

This attribute is a list of const ranges in ascending order with no
Copy link
Contributor

Choose a reason for hiding this comment

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

s/const/constant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, done!

since ``initialized`` allows reading from the pointer after writing.

This attribute is a list of const ranges in ascending order with no
overlapping or adjoining list elements. LoN/HiN are 64-bit ints, and
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't heard "adjoining" used in this context, I think "consecutive" is more commonly used.

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!

since ``initialized`` allows reading from the pointer after writing.

This attribute is a list of const ranges in ascending order with no
overlapping or adjoining list elements. LoN/HiN are 64-bit ints, and
Copy link
Contributor

Choose a reason for hiding this comment

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

put LoN/HiN in double backticks

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!

@nikic
Copy link
Contributor

nikic commented Mar 13, 2024

As a heads up, the inference implementation for this (or at least the available prototype) does not look viable from a compile-time perspective: https://llvm-compile-time-tracker.com/compare.php?from=69b09b43b0a2057918078edb401adab888d1014b&to=0bd68ae2d56783377acc0aa5d7958b47411b8342&stat=instructions:u

@haopliu
Copy link
Contributor Author

haopliu commented Mar 14, 2024

As a heads up, the inference implementation for this (or at least the available prototype) does not look viable from a compile-time perspective: https://llvm-compile-time-tracker.com/compare.php?from=69b09b43b0a2057918078edb401adab888d1014b&to=0bd68ae2d56783377acc0aa5d7958b47411b8342&stat=instructions:u

Thanks for this heads up! This PR only adds the attribute support. Will further tune the inferring performance and post the updated compile time tracker in the inference PR.

@haopliu
Copy link
Contributor Author

haopliu commented Mar 14, 2024

Thank you all! Update the LangRef, and add the attribute support. Please take another look :-D

@haopliu haopliu changed the title Add 'initialized' attribute langref Add the 'initialized' attribute langref and support Mar 14, 2024
@@ -38,6 +38,9 @@ class IntAttr<string S, list<AttrProperty> P> : Attr<S, P>;
/// Type attribute.
class TypeAttr<string S, list<AttrProperty> P> : Attr<S, P>;

/// Const range list attribute.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: put closer to the ConstantRangeAttr?

@@ -318,6 +321,9 @@ def Writable : EnumAttr<"writable", [ParamAttr]>;
/// Function only writes to memory.
def WriteOnly : EnumAttr<"writeonly", [ParamAttr]>;

/// Pointer argument memory [%p+LoN, %p+HiN) is initialized.
Copy link
Contributor

Choose a reason for hiding this comment

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

The list looks almost sorted, so may be better to put it near InAlloca or ?

@@ -307,6 +308,10 @@ namespace llvm {
bool AllowParens = false);
bool parseOptionalCodeModel(CodeModel::Model &model);
bool parseOptionalDerefAttrBytes(lltok::Kind AttrKind, uint64_t &Bytes);
bool parseConstRange(std::pair<int64_t, int64_t> &Range);
bool parseInitializedRanges(
lltok::Kind AttrKind,
Copy link
Contributor

Choose a reason for hiding this comment

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

can the AttrKind be hardcoded within the function instead of taking as parameter? (unlike DerefAttrBytes, there is only one variation of the attribute, I believe?)

@@ -94,6 +94,8 @@ class Attribute {

static const unsigned NumIntAttrKinds = LastIntAttr - FirstIntAttr + 1;
static const unsigned NumTypeAttrKinds = LastTypeAttr - FirstTypeAttr + 1;
static const unsigned NumConstRangeListAttrKinds =
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used by anything (if not, can remove)? I don't see the ConstantRangeAttrKind version of it, and hard to see "NumTypeAttrKinds" used

@@ -186,6 +193,9 @@ class Attribute {
/// Return true if the attribute is a type attribute.
bool isTypeAttribute() const;

/// Return true if the attribute is a const range list attribute.
bool isConstRangeListAttribute() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: wonder if should s/Const/Constant/ to be consistent with isConstantRangeAttribute, etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, no need to abbreviate

@@ -362,6 +362,21 @@ class FoldingSetNodeID {
}
}

void AddRanges(const SmallVector<std::pair<int64_t, int64_t>, 16> &Ranges) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use ArrayRef or something to not need specific SmallVector inline size of 16 here? "Prefer to use ArrayRef or SmallVectorImpl as a parameter type." under https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h

@@ -226,6 +236,10 @@ class Attribute {
/// attribute to be a ConstantRange attribute.
ConstantRange getValueAsConstantRange() const;

/// Return the attribute's value as a const range list. This requires the
/// attribute to be a const range list attribute.
SmallVector<std::pair<int64_t, int64_t>, 16> getValueAsRanges() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to return an ArrayRef instead of a copy? Will the underlying storage lifetime work out?

if (parseInt64(Range.first))
return true;

if (EatIfPresent(lltok::comma)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should it be an error to be missing the comma?

@@ -930,11 +932,24 @@ void ModuleBitcodeWriter::writeAttributeGroupTable() {
Record.push_back(getAttrKindEncoding(Attr.getKindAsEnum()));
if (Ty)
Record.push_back(VE.getTypeID(Attr.getValueAsType()));
} else {
} else if (Attr.isConstantRangeAttribute()) {
assert(Attr.isConstantRangeAttribute());
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can remove the assert?

Check(!Inits.empty(), "Attribute 'initialized' does not support empty list",
V);

for (size_t i = 1; i < Inits.size(); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps check low < high for the same "i"?

@@ -226,6 +236,10 @@ class Attribute {
/// attribute to be a ConstantRange attribute.
ConstantRange getValueAsConstantRange() const;

/// Return the attribute's value as a const range list. This requires the
/// attribute to be a const range list attribute.
SmallVector<std::pair<int64_t, int64_t>, 16> getValueAsRanges() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

we should use ConstantRange instead of std::pair, especially since it already has intersection/union methods

Copy link
Contributor

Choose a reason for hiding this comment

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

also, 16 elements for the small vector optimization seems very high, I feel the vast majority of cases will be at most 4. I'd choose 4 (or even 2)

@@ -616,6 +652,23 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
return Result;
}

if (hasAttribute(Attribute::Initialized)) {
auto Ranges = getValueAsRanges();
if (Ranges.empty())
Copy link
Contributor

Choose a reason for hiding this comment

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

is this allowed? I think we should forbid an empty list

Copy link
Contributor

Choose a reason for hiding this comment

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

oh I see the verifier check below, so I don't think we need this check

@@ -1993,6 +1993,14 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
Attrs.hasAttribute(Attribute::ReadOnly)),
"Attributes writable and readonly are incompatible!", V);

Check(!(Attrs.hasAttribute(Attribute::Initialized) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is true. A function that immediately unwinds can be marked as initializing all arguments according to our semantics, but is also readnone

llvm/docs/LangRef.rst Show resolved Hide resolved
@@ -186,6 +193,9 @@ class Attribute {
/// Return true if the attribute is a type attribute.
bool isTypeAttribute() const;

/// Return true if the attribute is a const range list attribute.
bool isConstRangeListAttribute() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, no need to abbreviate

parameter. Other arbitrary accesses to the same memory via other pointers
are allowed.

Note that this attribute does not apply to the unwind edge: the
Copy link
Contributor

Choose a reason for hiding this comment

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

this is now redundant with the unwind comment above

@nikic
Copy link
Contributor

nikic commented Mar 15, 2024

As a heads up, the inference implementation for this (or at least the available prototype) does not look viable from a compile-time perspective: https://llvm-compile-time-tracker.com/compare.php?from=69b09b43b0a2057918078edb401adab888d1014b&to=0bd68ae2d56783377acc0aa5d7958b47411b8342&stat=instructions:u

Thanks for this heads up! This PR only adds the attribute support. Will further tune the inferring performance and post the updated compile time tracker in the inference PR.

The main thing I would be concerned about at this point is whether the entire approach is viable or not -- I'd rather not add the attribute (plus a whole new attribute kind) if it later turns out that this is too expensive.


Anyway, a couple of high level comments:

  • I'm not sure this is the ideal attribute name. initialized to me sounds like this attribute promises that the memory is initialized on entry to the function, not that the function will initialize it. Maybe will_initialize is a better name? Or initializes?
  • How important is it to represent multiple ranges? We have just added support for ConstantRange attributes, so a single range can be represented out of the box. This also avoids the tricky question of how to deal with non-zero start offsets in DSE, which we don't really have AA support for right now. My intuition would be that you usually get whole object initializations -- but maybe this is not the case due to padding holes?
  • Failing that, I feel like we should still piggy-back more on the new ConstantRange attribute kind. We could convert that into a list of ConstantRange stored as TrailingObjects and make the current single-range case a special case of the general multiple-range case.

@aeubanks
Copy link
Contributor

aeubanks commented Mar 25, 2024

The main thing I would be concerned about at this point is whether the entire approach is viable or not -- I'd rather not add the attribute (plus a whole new attribute kind) if it later turns out that this is too expensive.

+1

Anyway, a couple of high level comments:

  • I'm not sure this is the ideal attribute name. initialized to me sounds like this attribute promises that the memory is initialized on entry to the function, not that the function will initialize it. Maybe will_initialize is a better name? Or initializes?

Makes sense, initializes sounds good.

  • How important is it to represent multiple ranges? We have just added support for ConstantRange attributes, so a single range can be represented out of the box. This also avoids the tricky question of how to deal with non-zero start offsets in DSE, which we don't really have AA support for right now. My intuition would be that you usually get whole object initializations -- but maybe this is not the case due to padding holes?

Yes the concern was padding holes. @haopliu do you have any data on how often padding prevents whole object initialization?

@aeubanks
Copy link
Contributor

Even if we represent padding more precisely with multiple ranges in initializes, clang generates a memset of the entire alloca and DSE can only remove the part of the memset that corresponds to the first range and the last range, since it doesn't split memsets. So if we had one padding hole and used multiple ranges to represent everything else, we'd still end up with a store to the padding hole.

I think !tbaa.struct was designed for this, but we don't emit it for -ftrivial-auto-var-init. Also it might not apply to memset right now, only memcpy?

@aeubanks
Copy link
Contributor

godbolt seems to not show metadata (I'm not seeing it on a memcpy on godbolt but am seeing it locally), but my point still stands

@aeubanks
Copy link
Contributor

actually I'm unsure if it would be legal to remove stores on padding bytes

@haopliu
Copy link
Contributor Author

haopliu commented Apr 25, 2024

A quick question before I look into this in more detail: Why do we need the dedicated ConstantRangeList class? I'd have expected to see this passed around as ArrayRef<ConstantRange> and stored as TrailingObjects<ConstantRange>.

I see it has some helpers to insert ranges in sorted order, but that seems more like something for the inference implementation rather than the attribute implementation.

Changed the attribute implementation to TrailingObjects<ConstantRange> + ArrayRef<ConstantRange>, and meanwhile kept the helper class ConstantRangeList for intersectWith/unionWith/... in FunctionAttrs.cpp and DSE.cpp.

Please take another look. Thanks!

@@ -1565,6 +1566,8 @@ class LLVMContextImpl {
UniqueStringSaver Saver{Alloc};
SpecificBumpPtrAllocator<ConstantRangeAttributeImpl>
ConstantRangeAttributeAlloc;
SpecificBumpPtrAllocator<ConstantRangeListAttributeImpl>
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems this isn't used anymore? Just using Alloc now?

Then the forward declaration of ConstantRangeListAttributeImpl above isn't needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, done!

private TrailingObjects<ConstantRangeListAttributeImpl, ConstantRange> {
friend TrailingObjects;

unsigned ValSize;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe just "Size" since there isn't a field named "Val"

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.

Ranges.insert(Ranges.begin(), NewRange);
return;
}
if (std::find(Ranges.begin(), Ranges.end(), NewRange) != Ranges.end()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think what Arthur meant (correct me if I'm wrong), is remove this std::find. The std::lower_bound below already does a "find" in the list.

Then check if LowerBound == NewRange. If so, then we've found a duplicate and can early return the way that this std::find() had previously done. I think that's what he meant by "check just one element" (the LowerBound element).

So then, also maybe you don't need the special case size() == 1 with overlap (not sure that's common).

@haopliu
Copy link
Contributor Author

haopliu commented May 6, 2024

Here is an updated compile time regression of the entire stack of patches:
this current PR + attribute inference with a dataflow approach (53bd30) + apply the attr in DSE (d8d842).
https://llvm-compile-time-tracker.com/compare.php?from=07d7b9c255078edc6f04bd4e68416bdf3e8735ab&to=d8d8420746ad988173adf5d71e9d51cd8d3975e7&stat=instructions%3Au

Copy link
Contributor

@aeubanks aeubanks left a comment

Choose a reason for hiding this comment

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

lgtm with one last nit

I'd like some other approvals before landing this

for the followup patches, the inference patch generally lgtm (since I wrote a lot of it :) ), and the DSE patch in principle makes sense (the important thing is that it allows an instruction to be associated with multiple MemoryDefs), although I still need to understand it better. 0.07% compile time regression for interprocedural DSE seems acceptable IMO

}
}

void ConstantRangeList::print(raw_ostream &OS) const {
Copy link
Contributor

Choose a reason for hiding this comment

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

doesn't really matter, they're the same


#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
LLVM_DUMP_METHOD void ConstantRangeList::dump() const {
print(llvm::dbgs());
Copy link
Contributor

Choose a reason for hiding this comment

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

remove llvm::

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!

Copy link
Contributor

@jvoung jvoung left a comment

Choose a reason for hiding this comment

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

Nice work reducing the compile time from the initial version =)

@@ -267,6 +281,9 @@ class Attribute {
/// Returns the value of the range attribute.
ConstantRange getRange() const;

/// Returns the value of the initializes attribute.
ConstantRangeList getInitializes() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Perhaps return ArrayRef to be consistent with getValueAsConstantRangeList(), then the caller can decide to make it a ConstantRangeList or not?

Also ArrayRef allows the caller to avoid a copy if it isn't needed.

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!

@aeubanks aeubanks requested a review from fhahn May 8, 2024 21:23
parameter. Other arbitrary accesses to the same memory via other pointers
are allowed.

The ``writable`` or ``dereferenceable`` attribute does not imply the
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The ``writable`` or ``dereferenceable`` attribute does not imply the
The ``writable`` or ``dereferenceable`` attribute do not imply the

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!

are allowed.

The ``writable`` or ``dereferenceable`` attribute does not imply the
``initializes`` attribute. The ``initializes`` does not imply ``writeonly``
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
``initializes`` attribute. The ``initializes`` does not imply ``writeonly``
``initializes`` attribute. The ``initializes`` attribute does not imply ``writeonly``

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!

since ``initializes`` allows reading from the pointer after writing.

This attribute is a list of constant ranges in ascending order with no
overlapping or consecutive list elements. ``LoN/HiN`` are 64-bit ints, and
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
overlapping or consecutive list elements. ``LoN/HiN`` are 64-bit ints, and
overlapping or consecutive list elements. ``LoN/HiN`` are 64-bit integers, and

Though I wonder whether the integers shouldn't match the pointer index type rather than being hardcoded to 64-bits...

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!

@@ -0,0 +1,86 @@
//===- ConstantRangeList.h - A list of constant range -----------*- C++ -*-===//
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
//===- ConstantRangeList.h - A list of constant range -----------*- C++ -*-===//
//===- ConstantRangeList.h - A list of constant ranges -----------*- C++ -*-===//

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!

//===----------------------------------------------------------------------===//
//
// Represent a list of signed ConstantRange and do NOT support wrap around the
// end of the numeric range. Ranges in the list are ordered and no overlapping.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// end of the numeric range. Ranges in the list are ordered and no overlapping.
// end of the numeric range. Ranges in the list are ordered and not overlapping.

or "non-overlapping".

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!


SmallVector<ConstantRange, 2> Val;
int RangeSize = Record[++i];
assert(i + 2 * RangeSize < e);
Copy link
Contributor

Choose a reason for hiding this comment

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

This can't be an assert, it should be an if with error return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, changed to an if with error return!

Record.push_back(Val.size());
for (auto &CR : Val) {
emitSignedInt64(Record, CR.getLower().getSExtValue());
emitSignedInt64(Record, CR.getUpper().getSExtValue());
Copy link
Contributor

Choose a reason for hiding this comment

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

This hardcodes the 64-bit ranges in the bitcode format. We shouldn't do that to allow future uses of this attribute type with different width. This should use emitConstantRange() instead, maybe after a small refactoring to allow emitting the bit width separately (as we don't need to emit it for each range...)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I wasn't imagining that a future attribute might want to use ConstantRangeList, but that does seem like something we should support.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to use emitConstantRange().

ConstantRangeListAttributeImpl::totalSizeToAlloc(Val),
alignof(ConstantRangeListAttributeImpl));
PA = new (Mem) ConstantRangeListAttributeImpl(Kind, Val);
pImpl->AttrsSet.InsertNode(PA, InsertPoint);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this ends up working fine right now where the ConstantRanges have <= 64 bits, but in general I think this may result in a memory leak due to the dtor not running.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How to call the dtor explicitly?

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm I'm not seeing where the existing ConstantRangeAttributeImpl's destructor gets called

Copy link
Contributor

Choose a reason for hiding this comment

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

ah this should be using a separate SpecificBumpPtrAllocator like ConstantRangeAttributeAlloc so that we make sure to call destructors when the LLVMContext is destroyed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh thanks for this info! Changed to SpecificBumpPtrAllocator. Please take another look :-)

// Represent a list of signed ConstantRange and do NOT support wrap around the
// end of the numeric range. Ranges in the list are ordered and no overlapping.
// Ranges should have the same bitwidth. Each range's lower should be less than
// its upper.
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't repeat the header comment here.

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!

llvm/include/llvm/IR/ConstantRangeList.h Show resolved Hide resolved
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

FWIW, what I originally had in mind was to actually not have separate ConstantRange and ConstantRangeList attribute kinds, and only have the latter -- the existing ConstantRange kind would just be a list with a single element.

The upside of doing that is that we have less attribute kinds to deal with :) The downside is that the representation for the single-attribute case becomes less efficient (because we have to explicitly store the size 1).

@aeubanks
Copy link
Contributor

aeubanks commented May 9, 2024

FWIW, what I originally had in mind was to actually not have separate ConstantRange and ConstantRangeList attribute kinds, and only have the latter -- the existing ConstantRange kind would just be a list with a single element.

The upside of doing that is that we have less attribute kinds to deal with :) The downside is that the representation for the single-attribute case becomes less efficient (because we have to explicitly store the size 1).

The less efficient single-range seems ok if the single range attributes aren't too common and have too many unique ranges. It's hard to predict the future to know if this will be the case. Supporting a separate ConstantRangeList attribute doesn't seem too bad though, so I'm leaning toward keeping them separate.

// Slow insert.
SmallVector<ConstantRange, 2> ExistingTail(LowerBound, Ranges.end());
Ranges.erase(LowerBound, Ranges.end());
// "sle" instead of "slt" to merge consecutive ranges.
Copy link
Contributor

Choose a reason for hiding this comment

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

the "sle" instead of "slt" portion of the comment isn't necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this portion!

ConstantRangeListAttributeImpl::totalSizeToAlloc(Val),
alignof(ConstantRangeListAttributeImpl));
PA = new (Mem) ConstantRangeListAttributeImpl(Kind, Val);
pImpl->AttrsSet.InsertNode(PA, InsertPoint);
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm I'm not seeing where the existing ConstantRangeAttributeImpl's destructor gets called

if (i + 2 * RangeSize >= e)
return error("Incomplete constant range list");
for (unsigned Idx = 0; Idx < RangeSize; ++Idx) {
int64_t Start = BitcodeReader::decodeSignRotatedValue(Record[++i]);
Copy link
Contributor

@jvoung jvoung May 17, 2024

Choose a reason for hiding this comment

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

The int64_t as type would still assume BitWidth <= 64 (if > 64, the writer actually writes more than 2 elements in the record per Idx)

Can you factor out part of readConstantRange() and reuse that (the part after having read the BitWidth from the record)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored readConstantRange and reused it to avoid the bitwidth assumption. Thanks!

@aeubanks
Copy link
Contributor

@nikic any more concerns?

@aeubanks
Copy link
Contributor

ping @nikic

bool empty() const { return Ranges.empty(); }

/// Get the bit width of this ConstantRangeList.
uint32_t getBitWidth() const { return 64; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Hardcoding this seems a bit odd for a generically named class like ConstantRangeList, but I guess this can always be relaxed later...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, relaxing later on demand is the plan!

// Represent a list of signed ConstantRange and do NOT support wrap around the
// end of the numeric range. Ranges in the list are ordered and not overlapping.
// Ranges should have the same bitwidth. Each range's lower should be less than
// its upper.
Copy link
Contributor

Choose a reason for hiding this comment

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

Something I noticed is that while ConstantRangeList maintains the "ordered and not overlapping" invariant through manipulations, it does not check that it initially holds. And indeed, LLParser will create such invalid ConstantRangeLists, which will later get rejected by the Verifier. Is this how it is intended to work out?

Copy link
Contributor

Choose a reason for hiding this comment

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

it is convenient that the verifier can check for sorted ConstantRangeLists without asserting on construction, but perhaps it is better to move the checks to both LLParser and bitcode parser

a static method that returns a std::optional<ConstantRangeList> depending on if the input is sorted or not, and error checking in the LLParser/bitcode parser would be good. then we can assert that the input is sorted in the constructor.

(or just automatically sort ConstantRangeLists on construction, but IMO the checks can be useful for finding bugs)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added static std::optional<ConstantRangeList> getConstantRangeList() and checked the optional return in LLParse and bitcode parser.


SmallVector<ConstantRange, 2> Val;
unsigned RangeSize = Record[++i];
unsigned BitWidth = Record[++i];
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing bounds check before these accesses.

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!

if (i + 2 * RangeSize >= e)
return error("Incomplete constant range list");
for (unsigned Idx = 0; Idx < RangeSize; ++Idx) {
i++;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really get why this i++ and i-- dance here is necessary. I think maybe you want just one ++i before the loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to before the loop and changed the Record access to "i++" rather than "++i", the same way as the "Range" attribute.

Record.push_back(Val[0].getBitWidth());
for (auto &CR : Val) {
emitConstantRange(Record, CR, /*EmitBitWidth=*/false);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove braces.

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!

llvm/lib/IR/AttributeImpl.h Outdated Show resolved Hide resolved
assert(Size > 0);
unsigned BitWidth = Val.front().getLower().getBitWidth();
for (unsigned I = 0; I != Size; ++I) {
assert(BitWidth == Val[I].getLower().getBitWidth());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assert(BitWidth == Val[I].getLower().getBitWidth());
assert(BitWidth == Val[I].getBitWidth());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this code snippet.

unsigned BitWidth = Val.front().getLower().getBitWidth();
for (unsigned I = 0; I != Size; ++I) {
assert(BitWidth == Val[I].getLower().getBitWidth());
new (&TrailingCR[I]) ConstantRange(BitWidth, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you use uninitialized_copy instead of performing a dummy initialization first?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this code snippet.

// If we didn't find any existing attributes of the same shape then create a
// new one and insert it.
PA = new (pImpl->ConstantRangeListAttributeAlloc.Allocate(
ConstantRangeListAttributeImpl::totalSizeToAlloc(Val)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Heh, this does something completely different from what you think it does. It allocates an array of sizeof(ConstantRangeListAttributeImpl) * totalSizeToAlloc(Val).

It's not possible to use SpecificBumpPtrAllocator with a dynamically sized class, it can only be used to allocate objects of fixed size.

Copy link
Contributor

Choose a reason for hiding this comment

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

if we have a variable number of ConstantRanges in a ConstantRangeListAttributeImpl and they need a destructor, I don't see a nice way of using trailing objects and bump allocators. I think we should go back to ConstantRangeListAttributeImpl containing a ConstantRangeList and using SpecificBumpPtrAllocator<ConstantRangeListAttributeImpl> so that we allocate the right amount of memory and properly call the destructor. most of the time we'll be in the non-allocating version of the SmallVector<ConstantRange> anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the attr implementation back to ConstantRangeList.

@haopliu
Copy link
Contributor Author

haopliu commented May 30, 2024

Thanks for the comments, Nikita and Arthur! @nikic please take another look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants