API review of VarHandles

Paul Sandoz paul.sandoz at oracle.com
Tue Feb 9 11:12:26 UTC 2016


Hi Jeremy,

Sorry for the late reply. Catching after some distractions and being away.


> On 22 Jan 2016, at 19:03, Jeremy Manson <jeremymanson at google.com> wrote:
> 
> Couple of thoughts:
> The Java Language Specification permits operations to be executed in orders different than are apparent in program source code, subject to constraints arising, for example, from the use of locks, volatile fields or VarHandles. The static methods, fullFence, acquireFence, releaseFence, loadLoadFence andstoreStoreFence, can also be used to impose constraints. Their specifications are phrased in terms of the lack of "reorderings" -- observable ordering effects that might otherwise occur if the fence were not present.
> 
> 
> There is no notion of reordering (per se) in the JLS; the fact that reorderings can occur is just implied by the way programs can be observed to behave. So, these fence operations don't map to anything non-implementation dependent.  I don't think it would be impossible to fix the spec to define something that works the way you want (similar to the final field semantics), but it isn't in there already.  You might want to call that out (either by saying this behavior is best effort implementation-dependent or by fixing the spec).
> 

Until we update the JMM spec, pushed out beyond 9, we are in a bit of a bind and have to hand wave a bit for both access mode methods and fences, both of which talk about reorderings (see getAcquire/setRelease for example).

There is the following at the end, but it’s easy to loose sight of with all the other sections:

* @apiNote
* More precise phrasing of the specification of access mode methods and memory
* fence methods may accompany future updates of the Java Language
* Specification.

I moved that up into the fences paragraph.

* <p>In addition to supporting access to variables under various access modes,
* a set of static methods, referred to as memory fence methods, is also
* provided for fine-grained control of memory ordering.
*
* The Java Language Specification permits operations to be executed in
* orders different than are apparent in program source code, subject to
* constraints arising, for example, from the use of locks, {@code volatile}
* fields or VarHandles.  The static methods, {@link #fullFence fullFence},
* {@link #acquireFence acquireFence}, {@link #releaseFence releaseFence},
* {@link #loadLoadFence loadLoadFence} and {@link #storeStoreFence
* storeStoreFence}, can also be used to impose constraints.  Their
* specifications, as is the case for certain access modes, are phrased in terms
* of the lack of "reorderings" -- observable ordering effects that might
* otherwise occur if the fence was not present.  More precise phrasing of the
* specification of access mode methods and memory fence methods may accompany
* future updates of the Java Language Specification.


I could append another sentence:

  Until then, such reordering behaviour is considered implementation-dependent on a best-effort basis.

But perhaps the less we say the better?


> If a VarHandle references a read-only variable (for example a final field) then write, atomic update and numeric atomic update access modes are not supported and corresponding methods throw UnsupportedOperationException.
> 
> Are you sure you want to limit it in this way?  There are an awful lot of calls to setAccessible in the world of reflection.  And writing to final fields *does* map to something sensible in the spec.  In fact, it would be great to have something that wasn't quite such a blunt instrument as setAccessible.

Indeed, very much a blunt instrument.

Note that setAccessible would otherwise be required to be called on the Field before being unreflected (either for unreflectVarHandle or, as is the case today, unreflectSetter/Getter).

I don’t wanna widen the accessibility hole of this blunt instrument right now; it’s easy to widen rather than shrink later on if need be.

The motivation behind this is we want to move towards a world "final really means final" and thus lots of nice optimisations may ensue. It may be a long and windy road to get there but we have some plans and are starting to work some routes towards that goal.


> 
> getVolatile
> public final Object getVolatile(Object... args)
> Returns the value of a variable, with memory semantics of reading a volatile variable.
> 
> Reading *which* volatile variable?  You probably mean that all getVolatiles and setVolatiles provide semantics that behave as if the variable being written / read was declared volatile in the first place, but it is worth calling out.
> 

/**
 * Returns the value of a variable, with memory semantics of reading as if
 * the variable was declared {@code volatile}.
...
Object getVolatile(Object... args);

/**
 * Sets the value of a variable to the {@code newValue}, with memory
 * semantics of setting as if the variable was declared {@code volatile}.
…
void setVolatile(Object... args);

I also updated the plain accessors using similar “as if” language.


> Nits:
> 

The following nits are copied and translated from documentation on MethodHandle.


> As is usual with virtual methods, source-level calls to access mode methods compile to an invokevirtual instruction. More unusually, the compiler must record the actual argument types, and may not perform method invocation conversions on the arguments. Instead, it must push them on the stack according to their own unconverted types. The VarHandle object itself is pushed on the stack before the arguments. The compiler then calls the VarHandle with a symbolic type descriptor which describes the argument and return types.
> 
> This is somewhat oddly worded.  The compiler doesn't push arguments on a stack or call anything - it generates instructions that do that.
> 

I updated to:

* As is usual with virtual methods, source-level calls to access mode methods
* compile to an {@code invokevirtual} instruction.  More unusually, the
* compiler must record the actual argument types, and may not perform method
* invocation conversions on the arguments.  Instead, it must generate
* instructions to push them on the stack according to their own unconverted
* types.  The VarHandle object itself will be pushed on the stack before the
* arguments.  The compiler then generates an {@code invokevirtual} instruction
* that invokes the access mode method with a symbolic type descriptor which
* describes the argument and return types.


> The first time a invokevirtual instruction is executed it is linked, by symbolically resolving the names in the instruction and verifying that the method call is statically legal
> 
> I keep trying not to call this out as a nit, but there should be no comma between "linked" and "by”.

Updated.

In both cases I also updated the relevant areas in MethodHandle.

Thanks,
Paul.


More information about the valhalla-dev mailing list