Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

assumeLocal: convert shared lvalue to a non-shared one #724

Closed
wants to merge 6 commits into from

Conversation

radcapricorn
Copy link
Contributor

This is one of the two functions to satisfy ER 12133 - Relaxed read-modify-write for shared lvalues.

/**
* Converts a shared lvalue to a non-shared lvalue.
*
* As the name suggests, this functions allows to treat a shared lvalue
Copy link

Choose a reason for hiding this comment

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

Kill off the As the name suggests part please (we're aiming for objective documentation). :)

Copy link

Choose a reason for hiding this comment

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

If this doesn't support classes it should document this. But why doesn't it support classes? That too should be documented.

@MartinNowak
Copy link
Member

Something is wrong here. Why do you make something shared if it's protected by a mutex?
Atomic access without the mutex would become a failure if you use assumeLocal with the mutex.

@radcapricorn
Copy link
Contributor Author

Consider member variables inside a shared class (i.e. Condition implementation for Windows). They're transitively shared. But if you only ever access such variables under a lock, you don't need explicit atomicOps at all.

It's true that accessing the variable outside of the lock will be erroneous, but this function is a convenience for cases when such access is not needed. See e.g. my ongoing branch implementing shared for all core.sync primitives.

Some kind of workaround is needed. Lots of casts or chains of raw atomicLoad/Store calls IMO are inferior to this little helper.

@MartinNowak
Copy link
Member

What's with automatic HeadUnshared for synchronized classes?
Looks like it's not implemented (http://dpaste.dzfl.pl/3e4d6b59f06a).

I don't want to slow down you efforts to make core.sync shared and I think assumeLocal is a useful primitive.
But please note that the shared concept is still fuzzy. As Andrei mentioned we need to finish the specs and implementation.
It would be great if you could help with that.

@radcapricorn
Copy link
Contributor Author

What's with automatic HeadUnshared for synchronized classes?

Even if it were implemented in that exact wording, it would be of little help in many cases. I know that in the grand scheme, synchronized is supposed to be used for lock-based synchronization and shared - for lock-free. But that is a very restrictive generalization. For example, making Semaphores or Conditions synchronized classes would be an overkill... no, an error (deadlocks), yet they should support shared (which they currently don't). Additionally, I bet there will be cases when using classes is not an option but some form of synchronization is needed.

But please note that the shared concept is still fuzzy. As Andrei mentioned we need to finish the specs and implementation.

I know, that is my thread :)

It would be great if you could help with that.

I'm trying to.

@@ -1090,6 +1136,13 @@ if(__traits(isFloating, T))
}
}

// assumeLocal is architecture-independent: it is just a cast
ref auto assumeLocal(T)( ref shared T val ) @trusted pure nothrow
if( !is( T == class ) )
Copy link
Member

Choose a reason for hiding this comment

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

 if (!is(T == class))

@andralex
Copy link
Member

Fine, but let's call it assumeThreadLocal because one may think it has to do with scopes. Please follow the phobos conventions in spacing.

@andralex
Copy link
Member

Take that back, should be assumeUnshared because that's the actual assumption.

@MartinNowak
Copy link
Member

ping

@dnadlinger
Copy link
Member

+1 for assumeUnshared.

@radcapricorn
Copy link
Contributor Author

I apologize for so long an absence, got caught up in a no-D world. So, rename to assumeUnshared, remove @trusted, rebase, commit?

@andralex
Copy link
Member

yes please

@MartinNowak
Copy link
Member

Ping

@DmitryOlshansky
Copy link
Member

@radcapricorn any chance to revive this? Seems like non-controversial stuff that just fell off the radar.

@DmitryOlshansky
Copy link
Member

Ping @radcapricorn ?

@radcapricorn
Copy link
Contributor Author

Three years ><

ref T assumeUnshared(T)(ref shared T val) @system @nogc pure nothrow
if(!is(T == class) && !is(T == interface))
{
return *cast(T*)&val;
Copy link
Member

Choose a reason for hiding this comment

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

space after close paren in cast

* Returns:
* The non-shared lvalue.
*/
ref immutable(T) assumeUnshared(T)(ref immutable(T) val) @safe @nogc pure nothrow
Copy link
Member

Choose a reason for hiding this comment

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

What would be a use case for this? It doesn't change the type of its argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Generic code.

shared struct Example(T)
{
    private T payload;

    T getUnsharedCopyOfPayload() const
    {
        return payload.assumeUnshared;
    }
}

auto createExample(T)(auto ref T value)
{
    return shared(Example!T)(value);
}

immutable int i = 42;
auto example = createExample(i);
assert(is(typeof(example.getUnsharedCopyOfPayload()) == immutable(int));

This way, Example doesn't have to specialize such use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IOTW, it's safe to assume immutable as unshared. But the signature ref T assumeUnshared(T)(ref shared T val) will not accept immutable lvalue, hence the specialization.

Copy link
Member

Choose a reason for hiding this comment

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

immutable is actually implicitly shared

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly, and assumeUnshared should accept shared arguments, therefore it shouldn't fail to compile with immutable argument. It works as advertised: takes shared lvalue and yields lvalue that can be treated as unshared. It just so happens that immutable is both at the same time.

Copy link
Member

Choose a reason for hiding this comment

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

@andralex @radcapricorn anything else still not clear?

Copy link
Member

@andralex andralex Sep 7, 2017

Choose a reason for hiding this comment

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

I'm still unconvinced about the usefulness. By that logic we should also define assumeUnshared for regular data so assumeUnshared works transparently with all data types (after all unshared data can be assumed to be unshared!)

One possibility is to not introduce this overload yet, and see how necessary it is in practice.

At any rate, if we do introduce the overload the documentation must be merged with the other overload's.

Copy link
Member

@andralex andralex left a comment

Choose a reason for hiding this comment

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

This is a good addition. A few nits need fixing.

ref T assumeUnshared(T)(ref shared T val) @system @nogc pure nothrow
if(!is(T == class) && !is(T == interface))
{
return *cast(T*) &val;
Copy link
Member

Choose a reason for hiding this comment

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

This is the ddoc version, right? Then it should not have an implementation.

* Returns:
* The non-shared lvalue.
*/
ref immutable(T) assumeUnshared(T)(ref immutable(T) val) @safe @nogc pure nothrow
Copy link
Member

@andralex andralex Sep 7, 2017

Choose a reason for hiding this comment

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

I'm still unconvinced about the usefulness. By that logic we should also define assumeUnshared for regular data so assumeUnshared works transparently with all data types (after all unshared data can be assumed to be unshared!)

One possibility is to not introduce this overload yet, and see how necessary it is in practice.

At any rate, if we do introduce the overload the documentation must be merged with the other overload's.

@@ -1290,6 +1357,19 @@ if(__traits(isFloating, T))
}
}

// assumeUnshared is architecture-independent: it is just a cast
ref auto assumeUnshared(T)(ref shared T val) @system @nogc pure nothrow
if(!is(T == class) && !is(T == interface))
Copy link
Member

Choose a reason for hiding this comment

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

obey phobos, if flush to the left and space after it

@wilzbach
Copy link
Member

Revived this to #2156

@wilzbach wilzbach closed this Mar 29, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs Rebase needs a `git rebase` performed Needs Work stalled
Projects
None yet
9 participants