[J3] Question about LOCK_TYPE specifications

Brad Richardson everythingfunctional at protonmail.com
Thu Jul 25 15:00:42 UTC 2024


Hi Malcolm,

Thanks for your response.

On Thu, 2024-07-25 at 07:40 +0900, Malcolm Cohen via J3 wrote:
> Hi Brad,
> 
> > A lock variable is unlocked...
> 
> This is the definition of what is locked or unlocked. It is not a
> constraint.
> 
> > All components have default initialization.
> 
> That is how we achieve the state that lock variables are defined (in
> the variable definition sense). It has nothing to do with the
> structure constructor - as you note, you cannot do anything
> interesting with the constructor; you can still do uninteresting
> things.
> 
> Those two specifications work together to achieve lock variables
> being initially unlocked, no matter how they come into existence.
> 

Why limit so strongly what constitutes an unlocked state? And why
dictate that a lock variable must be *fully* default initialized? There
are certainly other states an implementation could consider to be
unlocked and the user wouldn't be able to tell. And surely a partially
defined lock variable could still be usable in a LOCK or UNLOCK
statement.

> > a bit over specified
> 
> And your point is?
> 
> I cannot think of anything user-visible that these requirements and
> definitions unnecessarily prevent. If have something in mind, please
> enlighten us.
> 
> In the absence of user visibility, what is the implementation method
> that you want to use but which is forbidden by the requirements?
> 

We don't have a design fully fleshed out, but one could imagine how
something like the following might be useful.

type :: lock_type
  private
  logical :: locked = .false.
  integer :: locked_by
end type

There is absolutely no reason to require the locked_by component to be
default initialized (it will not be referenced before being locked),
nor should it be required to be reset when unlocked in order for the
lock variable to be considered unlocked.

Another important bit is that efficient and fair implementations will
want to keep track of who is waiting for a lock. I.e.

type :: lock_type
  private
  logical :: locked = .false
  integer :: locked_by
  integer, allocatable :: waiting_for_lock(:)
end type

But with the prohibition on allocatable components, that implementation
would be invalid. There's a way around the allocatable component, use a
type(c_ptr) component and do stuff in C to work with it, but that
doesn't quite get around the "only the default state is unlocked". It
probably wouldn't be a problem, but a user could in theory tell by
using something like:

print *, transfer(lock_var, [integer::])
lock (lock_var)
unlock (lock_var)
print *, transfer(lock_var, [integer::])

to see that the internal representation isn't necessarily identical
before and after the lock/unlock sequence.

> I will note that the purpose of these specifications is to ensure
> that lock variables work the way we want them to. Those
> specifications are probably sufficient, there is only a point in
> restricting them to what is strictly necessary if that would enable
> something useful. It is frequently the case that minimum conditions
> are more complicated than sufficient conditions, especially when it
> comes to comprehension and proving sufficiency.
> 
> > Why must they be *fully* default-initialized?
> 
> So they have defined values. We do not want holes in the definition.
> Holes mean the standard does not provide an interpretation. That
> would make it far more likely that the standard is ambiguous or
> contradictory. In my opinion, this particular requirement must remain
> as is.
> 
> > similar constraint that "Each nonallocatable component is fully
> > default- initialized."
> 
> Once again, with feeling: that is not a "constraint". I know that in
> English there is wide ambiguity so one can use "constraint",
> "requirement", "rule" and at least a dozen other words
> simultaneously. When talking about the standard, however, we should
> strive to use words defined by the standard or by ISO more carefully.
> 
> Finally, a future revision can always relax requirements without
> serious risk of incompatibility. Thus, as long as the requirements
> specify the facility sufficiently well for both users and
> implementors, "overly strict" is not a problem.
> 
> Being "insufficiently strict" however is a big problem, for everyone:
> users, implementors, and future revisions. Tightening restrictions
> almost always introduces incompatibilities and the likelihood of some
> implementations having to change their behaviour (possibly even in
> user-visible ways), or worse: some implementations simply never
> bother to update their implementation of the feature to the new
> version, rendering the feature unreliable in actual use. That does
> not " promote portability, reliability, maintainability, and
> efficient execution" (our purpose!).
> 
> And so when I ask "what implementation method is it that you want to
> use but which is forbidden", I am very much open to any suggestions
> you might have, but that would be immediately followed by "how do you
> propose to specify things to provably maintain existing semantics".

The fully default initialized is probably not a problem to satisfy in
an implementation (if unnecessary), but I really do want to be able to
have a pointer or allocatable component in LOCK_TYPE, and I don't want
to have to fully reset that component for the lock to be considered
unlocked. I would suggest something like the following edits:

[page 224: paragraph 2] delete the first two sentences:

"A lock variable is unlocked if and only if the value of each component
is the same as its default value. If it has any other value, it is
locked."

[page 461: paragraph 1] delete the last phrase of the first sentence:

"; no component is allocatable or a pointer"

[page 461: paragraph 1] change the last sentence to:

Each nonallocatable component is fully default-initialized to an
unlocked state.

[page 461: paragraph 2] change the 3rd and 4th sentences to

The initial state of a lock variable is unlocked.

I'm happy to hear about other options or suggestions, or that an
implementation need not worry about the backdoor observability using
`transfer`, so long as it "meets the spirit" of the specification in
the standard.

Regards,
Brad

> 
> Cheers,
> --
> ..............Malcolm Cohen, NAG Oxford/Tokyo.
> 
> -----Original Message-----
> From: J3 <j3-bounces at mailman.j3-fortran.org> On Behalf Of Brad
> Richardson via J3
> Sent: Thursday, July 25, 2024 4:48 AM
> To: General J3 interest list <j3 at mailman.j3-fortran.org>
> Cc: Brad Richardson <everythingfunctional at protonmail.com>
> Subject: [J3] Question about LOCK_TYPE specifications
> 
> Hi all,
> 
> We were working through some things and ran into a question about
> some of the constraints and specifications involving LOCK_TYPE, which
> also tangentially involve EVENT_TYPE, NOTIFY_TYPE and TEAM_TYPE. It
> seems like they are a bit over specified, so I'd be curious if
> anybody knows the rationale for some of them.
> 
> First, from Sec 11.7.10 LOCK and UNLOCK statements its says that
> 
> > A lock variable is unlocked if and only if the value of each
> component is the same as its default value. If it has any other
> value, it is locked.
> 
> This seems to overly constrain possible implementations. Given that
> there is no way to determine whether a LOCK_TYPE variable is locked
> or unlocked other than to try and lock it, what does this constraint
> achieve? It's worth noting that there's also no other way to change
> the value of a LOCK_TYPE variable, as LOCK and UNLOCK statements are
> the only place they can appear in a variable definition context.
> 
> Next, in 16.10.2.19 LOCK_TYPE it says:
> 
> > LOCK_TYPE is a derived type with private components; no component
> > is
> allocatable or a pointer. It is an extensible type with no type
> parameters. All components have default initialization.
> > 
> > ... The unlocked state is represented by the one value that is the
> default value of a LOCK_TYPE variable; this is the value specified by
> the structure constructor LOCK_TYPE ( )
> 
> Why the restriction to no allocatable or pointer components, or that
> they all have default initialization? Also, the last sentence implies
> that it's valid to use the intrinsic structure constructor for
> LOCK_TYPE with no arguments, but I'm not sure why you'd want that,
> since you can't do anything meaningful with the value it returns. You
> can't assign it to anything or pass it as an argument to any
> procedures since "A named variable with declared type LOCK_TYPE shall
> be a coarray" and "A lock variable shall not appear in a variable
> definition context".
> 
> EVENT_TYPE, NOTIFY_TYPE and TEAM_TYPE are seemingly allowed to have
> allocatable or pointer components, but still have the similar
> constraint that "Each nonallocatable component is fully default-
> initialized." Why must they be *fully* default-initialized? Is this
> to allow use of intrinsic structure constructors with no arguments?
> If so why? You can't do anything with the values they return.
> 
> If anybody has some background info on why these restrictions on an
> implementation are there I'd greatly appreciate it.
> 
> Regards,
> Brad Richardson
> 
> 




More information about the J3 mailing list