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

[stdlib] Implement Weak[T] refs for Arc[T] #3450

Open
wants to merge 13 commits into
base: nightly
Choose a base branch
from

Conversation

szbergeron
Copy link

This implements a Weak[T] non-owning ref for Arc[T] that should allow for more ergonomic implementation of cyclic data structures without having to fall back to UnsafePointer[T] or Optional[Arc[T]] + complex directed drop impls.

This increases the footprint of _ArcInner by (minimally) 8 bytes. If we wish to cut this down, specialization and conditional inclusion of the field (once implemented) may allow for Arc[T, Kind=StrongOnly] constructs.

Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
@szbergeron szbergeron requested a review from a team as a code owner September 4, 2024 23:17
@szbergeron szbergeron changed the title Implement Weak[T] refs for Arc[T] [stdlib] Implement Weak[T] refs for Arc[T] Sep 4, 2024
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
@szbergeron
Copy link
Author

Needs some fixes after parameterizing Arc for Weak, but recent compiler changes have fixed the parameter propagation/inference issues so it's just a matter of fixing the broken ref count.

Copy link
Collaborator

@ConnorGray ConnorGray left a comment

Choose a reason for hiding this comment

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

Hey @szbergeron, thanks for tackling this! 🙂 This will be a nice QoL thing for folks doing self-referential data structures in Mojo.

I've left a few initial comments on the implementation strategy, particularly about how we might be able to make stronger guarantees around the enable_weak: Bool parameter using the type system.

Let me know if my suggestions are workable! 🙂

Thanks again for your contribution, awesome stuff 🔥

T: The type of the value that this Weak may be upgraded into an Arc[T] of.
"""

alias _inner_type = _ArcInner[T]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question Should this be:

Suggested change
alias _inner_type = _ArcInner[T]
alias _inner_type = _ArcInner[T, True]

since this Weak must have been derived from an Arc[T, True]?

return self._inner[].payload
return self._inner[].payload.assume_initialized()

fn downgrade(self) -> Weak[T, enable_weak]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
fn downgrade(self) -> Weak[T, enable_weak]:
fn downgrade(self: Arc[T, True]) -> Weak[T]:

else:
constrained[False, "A typical Arc[T] does not support downgrading." +
"If you want to enable downgrading of this Arc[T], convert it to an Arc[T, enable_weak = True]"]()
return abort[Weak[T, enable_weak]]("unreachable")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion By constraining self: Arc[T, True (shown in my comment above), I think this whole statement could be simplified to just:

Suggested change
return abort[Weak[T, enable_weak]]("unreachable")
return Weak(self)

Copy link
Author

Choose a reason for hiding this comment

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

I like the simplification, but is there a good way with that change of presenting specific, actionable instructions to the user that what they are trying to do can only be done if they make their Arc enable_weak = True?

@@ -110,6 +309,16 @@ struct Arc[T: Movable](CollectionElement, CollectionElementNew):
other._inner[].add_ref()
self._inner = other._inner

fn __init__(inout self, ptr: UnsafePointer[Self._inner_type], private: ()):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion We could annotate this with @doc_private instead of using a private parameter.

@@ -24,7 +24,7 @@ from memory import UnsafePointer, bitcast
from sys.info import triple_is_nvidia_cuda


struct Atomic[type: DType]:
struct Atomic[type: DType](ExplicitlyCopyable):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question Why is this needed?

Copy link
Author

Choose a reason for hiding this comment

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

This was to allow me to construct the [0|1] length array for weak. I wanted to revisit the specifics, since having an array of atomics can be useful in a lot of contexts but there isn't currently a good way of constructing one without arcane MaybeUninit incantations. I do have some questions about the atomic type that can be had out-of-thread too, that (I think) make Arc technically unsound in a single threaded context and relatively impossible to use in a multi-threaded one

alias _inner_type = _ArcInner[T]
var _inner: UnsafePointer[Self._inner_type]

fn __init__(inout self, strong: Arc[T, enable_weak]):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion Likewise, we can change this initializer so that Weak[T] can only be constructed frfom an Arc[T, True]:

          fn __init__(inout self, strong: Arc[T, True]):

This would let you remove the constrained[enable_weak, "..."]() below as well 🙂

other._inner[].add_weak()
self._inner = other._inner

fn upgrade(inout self) -> Optional[Arc[T]]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion I think this needs to encode that this is an Arc containing a weak count:

Suggested change
fn upgrade(inout self) -> Optional[Arc[T]]:
fn upgrade(inout self) -> Optional[Arc[T, True]]:

(self._inner).destroy_pointee()
self._inner.free()
self._inner = UnsafePointer[Self._inner_type]()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Request This "nulling out" of fields is not required within __del__() methods. After the __del__() completes, this object instance is no longer alive, so there's no code that could observe self._inner one way or the other 🙂


alias _weak_refcount_type = __mlir_type[
`!pop.array<`, Int(enable_weak).value, `, `, Atomic[DType.int64], `>`
]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment This is clever using the boolean to make this a zero-sized field, neat trick! 🙂

"""
self.payload.assume_initialized_destroy()

fn try_upgrade_weak(inout self) -> Bool:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion I think this could use the "custom self types" / "conditional conformance" trick to be statically constrained to:

Suggested change
fn try_upgrade_weak(inout self) -> Bool:
fn try_upgrade_weak(inout self: Arc[T, True]) -> Bool

and then the @parameter if enable_weak statement below could be simplified.

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

Successfully merging this pull request may close these issues.

2 participants