Hi Uwe,

If you’re targetting JDK 9 only, you could also consider basing your language’s 
dynamic operations on Dynalink; it’s integrated into JDK 9 and is specifically 
meant as a way to ease implementation of languages that have dynamic types. It 
happens to have a predefined GET_LENGTH[1] operation that does exactly what you 
want when BeansLinker[2] applies it to Java arrays. 

The documentation can be found at 
<http://download.java.net/java/jdk9/docs/jdk/api/dynalink 
<http://download.java.net/java/jdk9/docs/jdk/api/dynalink//jdk/dynalink/StandardOperation.html#GET_LENGTH>>;
 the jdk.dynalink package description is a good starting point (for some reason 
the main package is listed in a separate “Other Packages” section on the 
overview page… probably a Javadoc glitch w/regard to Jigsaw)

Attila.

[1] 
http://download.java.net/java/jdk9/docs/jdk/api/dynalink//jdk/dynalink/StandardOperation.html#GET_LENGTH
 
<http://download.java.net/java/jdk9/docs/jdk/api/dynalink//jdk/dynalink/StandardOperation.html#GET_LENGTH>
[2] 
http://download.java.net/java/jdk9/docs/jdk/api/dynalink/jdk/dynalink/beans/BeansLinker.html

> On 11 May 2016, at 21:35, Uwe Schindler <uschind...@apache.org> wrote:
> 
> Hi,
> 
> while working and trying the new JDK9 MethodHandles features like 
> MethodHandles#countedLoop, I recognized a API inconsistency problem with it.
> 
> We also found this while implementing the "Painless" scripting language for 
> Elasticsearch (see https://goo.gl/DbOzjC)! Painless is a very limited 
> scripting language with a very limited subset of classes to access and close 
> integration into Elasticsearch (similar to Lucene's Expressions Module). Both 
> compile script code to anonymous classes and Elasticsearch uses invokedynamic 
> for the untyped stuff. It is much faster than Groovy or Nashorn, because it 
> compiles most of the code completely static with known types (like Lucene 
> expressions), but also supports a Groovy-like "def" type. The latter one uses 
> invokedynamic, everything else is exactly the same speed as native Java code.
> 
> During implementing array getters and setters for untyped stuff with 
> invokedynamic we recognized the following problem (Java 8 code, see 
> https://github.com/elastic/elasticsearch/pull/18232):
> 
> There is MethodHandles.arrayElementGetter and 
> MethodHandles.arrayElementSetter, so we can have full speed and full dynamic 
> flexibility for array loads and stores - the speed is great, same as native 
> array access! But there is one factory method missing in Java 8: 
> MethodHandles.arrayLengthGetter (or similar name). As "length" is not a field 
> on the array class, it is impossible to create a "simple" MethodHandle as 
> field access to "array.class#length" to access it (it must invoke the 
> "arraylength" bytecode, there is no field). I think the "length" field is 
> just syntactic sugar of javac compiler.
> 
> I know that Nashorn used to use java.lang.reflect.Array#getLength (not sure 
> if this is still the case), but this makes usage inconsistent. You have to 
> explicitly create a MethodHandle using the reflective array class to use it: 
> And it is not strongly typed (it accepts Object). A real MethodHandle would 
> be created for a specific type of array class and would return its length 
> without runtime type checks. I know that at least Array.getLength() is 
> optimized by Hotspot (in contrast to the other get/set methods, which are 
> like 20 times slower than a method access, so 
> MethodHandles.arrayElementGetter/Setter is very useful), but I would really 
> like to suggest to fix this!
> 
> With Java 9 this gets a bit worse: There is no "easy way" with the 
> MethodHanldes class to generate a MethodHandles.countedLoop() on all elements 
> of an array:
> 
> public static MethodHandle countedLoop(MethodHandle iterations, MethodHandle 
> init, MethodHandle  body) [new in Java 9]
> 
> With a full-featured API one could write:
> 
> Class<?> type = arraytype, e.g. long[].class or String[].class
> MethodHandle body = some code that uses 
> MethodHandles.arrayElementGetter(type) for further processing
> MethodHandles.countedLoop(MethodHandles.arrayLengthGetter(type), 
> MethodHandles.constant(int.class, 0), body);
> 
> As you see, for the first parameter (count), you would need to use the 
> reflective part in java.lang.reflect.Array if the method is still missing in 
> Java 9. This is not bad here, because it is not called all the time, but for 
> our scripting language, the reflective class was slower.
> 
> We implemented our own version of "arrayLengthGetter":
> 
> public  class ArrayLengthHelper {
>  private ArrayLengthHelper() {}
> 
>  private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
>  private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = 
> Collections.unmodifiableMap(
>    Stream.of(boolean[].class, byte[].class, short[].class, int[].class, 
> long[].class, char[].class, float[].class, double[].class, Object[].class)
>      .collect(Collectors.toMap(Function.identity(), type -> {
>        try {
>          return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), 
> "getArrayLength", MethodType.methodType(int.class, type));
>        } catch (ReflectiveOperationException e) {
>          throw new AssertionError(e);
>        }
>      }))
>  );
>  private static final MethodHandle OBJECT_ARRAY_MH = 
> ARRAY_TYPE_MH_MAPPING.get(Object[].class);
> 
>  static int getArrayLength(boolean[] array) { return array.length; }
>  static int getArrayLength(byte[] array) { return array.length; }
>  static int getArrayLength(short[] array) { return array.length; }
>  static int getArrayLength(int[] array) { return array.length; }
>  static int getArrayLength(long[] array) { return array.length; }
>  static int getArrayLength(char[] array) { return array.length; }
>  static int getArrayLength(float[] array) { return array.length; }
>  static int getArrayLength(double[] array) { return array.length; }
>  static int getArrayLength(Object[] array) { return array.length; }
> 
>  public static MethodHandle arrayLengthGetter(Class<?> arrayType) {
>    if (!arrayType.isArray()) {
>      throw new IllegalArgumentException("type must be an array");
>    }
>    return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
>        ARRAY_TYPE_MH_MAPPING.get(arrayType) :
>        OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, 
> arrayType));
>  }
> }
> 
> Interestingly I later found out that MethodHandles.arrayElementGetter/Setter 
> uses the same "trick" behind the scenes! See MethodHandleImpl.ArrayAccessor: 
> http://goo.gl/94f6OB
> 
> I would suggest to add the missing Method to MethodHandles class and 
> implement it together with the getters and setters in ArrayAccessor, similar 
> to our example code (it is just a few lines more). In addition this one could 
> then also use the extra intrinsic improvement that our class cannot use: 
> makeIntrinsic(getAccessor(Object[].class, false), Intrinsic.ARRAY_LOAD);
> [with another intrinsic added: Intrinsic.ARRAY_LENGTH for the arraylength 
> bytecode]
> 
> What would be the process to propose such a change? Bug/Issue/JEP?
> I could quickly create a patch, but fixing the Intrinsic/LambdaForm stuff is 
> way too complicated for me, so I would suggest to take this as an inspiration 
> how to do it
> 
> Uwe
> 
> -----
> Uwe Schindler
> uschind...@apache.org 
> ASF Member, Apache Lucene PMC / Committer
> Bremen, Germany
> http://lucene.apache.org/
> 
> 

Reply via email to