A small patch to use the StringConcatFactory instead of String.format() to implement toString() of a data class

Brian Goetz brian.goetz at oracle.com
Mon Nov 13 20:38:25 UTC 2017


Thanks!  We are refining a combo test to validate these sorts of changes.  

Performance tweaks are low priority for now, and we’ll need to chose based on realistic Performance measurements. I am sure the version I wrote is much slower than optimal so I doubt well be keeping that one. But if anybody wants to work on a performance testing harness for code gen options here that would be great!

Sent from my iPad

> On Nov 13, 2017, at 5:58 PM, Vicente Romero <vicente.romero at oracle.com> wrote:
> 
> Hi Remi,
> 
> Thanks for the patch, seems useful. We could add it as an optional implementation and make a comparison between the possible implementations. Another possible implementation, again in the case of the toString, is to generate an indy call directly from the data class to a StringConcatFactory method,
> 
> Thanks,
> Vicente
> 
>> On 11/12/2017 04:58 PM, Remi Forax wrote:
>> Hi all,
>> A small patch to use the StringConcatFactory to implement the toString of a data class.
>> 
>> The patch manage the case where there is no getter like the current code, even if you can not create a data class with no field using javac.
>> 
>> cheers,
>> Rémi
>> 
>> diff -r a6a70e4541b3 src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java
>> --- a/src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java    Fri Nov 10 18:51:06 2017 +0100
>> +++ b/src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java    Sun Nov 12 22:54:42 2017 +0100
>> @@ -189,49 +189,50 @@
>>        /**
>>       * Generates a method handle for the {@code toString} method for a given data class
>> +     * @param lookup          the lookup object
>>       * @param receiverClass   the data class
>>       * @param getters         the list of getters
>>       * @param names           the names
>>       * @return the method handle
>>       */
>> -    private static MethodHandle makeToString(Class<?> receiverClass,
>> +    private static MethodHandle makeToString(MethodHandles.Lookup lookup,
>> +                                            Class<?> receiverClass,
>>                                              List<MethodHandle> getters,
>>                                              List<String> names) {
>> -        // This is a pretty lousy algorithm; we spread the receiver over N places,
>> -        // apply the N getters, apply N toString operations, and concat the result with String.format
>> -        // Better to use String.format directly, or delegate to StringConcatFactory
>> -        // Also probably want some quoting around String components
>> +        // This is a simple algorithm; we spread the receiver over N places,
>> +        // apply the N getters, then delegate to StringConcatFactory
>> +        // We may want some quoting around String components ?
>>            assert getters.size() == names.size();
>>  -        int[] invArgs = new int[getters.size()];
>> -        Arrays.fill(invArgs, 0);
>> -        MethodHandle[] filters = new MethodHandle[getters.size()];
>> -        StringBuilder sb = new StringBuilder();
>> -        sb.append(receiverClass.getSimpleName()).append("[");
>> -        for (int i=0; i<getters.size(); i++) {
>> -            MethodHandle getter = getters.get(i); // (R)T
>> -            MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String
>> -            MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter);    // (R)String
>> -            filters[i] = stringifyThisField;
>> -            sb.append(names.get(i)).append("=%s");
>> -            if (i != getters.size() - 1)
>> -                sb.append(", ");
>> -        }
>> -        sb.append(']');
>> -        String formatString = sb.toString();
>> -        MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString)
>> -                                              .asCollector(String[].class, getters.size()); // (R*)String
>> -        if (getters.size() == 0) {
>> -            // Add back extra R
>> -            formatter = MethodHandles.dropArguments(formatter, 0, receiverClass);
>> -        }
>> -        else {
>> -            MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters);
>> -            formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs);
>> +        if (getters.isEmpty()) {
>> +            return MethodHandles.dropArguments(
>> +                MethodHandles.constant(String.class, "[]"),
>> +                0, receiverClass);
>>          }
>>  -        return formatter;
>> +        Class<?>[] getterTypes = new Class<?>[getters.size()];
>> +        StringBuilder recipeBuilder = new StringBuilder();
>> +        recipeBuilder.append(receiverClass.getSimpleName()).append('[');
>> +        String separator = "";
>> +        for (int i = 0; i < getters.size(); i++) {
>> +            recipeBuilder.append(separator).append(names.get(i)).append("=\1");
>> +            getterTypes[i] = getters.get(i).type().returnType();
>> +            separator = ", ";
>> +        }
>> +        String recipe = recipeBuilder.append(']').toString();
>> +        MethodType concatType = MethodType.methodType(String.class, getterTypes);
>> +
>> +        CallSite cs;
>> +        try {
>> +            cs = StringConcatFactory.makeConcatWithConstants(lookup, "toString", concatType, recipe);
>> +        } catch (StringConcatException e) {
>> +            throw (LinkageError)new LinkageError().initCause(e);
>> +        }
>> +        MethodHandle target = cs.dynamicInvoker();
>> +        target = MethodHandles.filterArguments(target, 0, getters.toArray(new MethodHandle[0]));
>> +        target = MethodHandles.permuteArguments(target, MethodType.methodType(String.class, receiverClass), new int[getters.size()]);
>> +        return target;
>>      }
>>        /**
>> @@ -277,6 +278,6 @@
>>       */
>>      public static CallSite makeToString(MethodHandles.Lookup lookup, String invName, MethodType invType,
>>                                          Class<?> dataClass, String names, MethodHandle... getters) throws Throwable {
>> -        return new ConstantCallSite(makeToString(dataClass, List.of(getters), List.of(names.split(";"))));
>> +        return new ConstantCallSite(makeToString(lookup, dataClass, List.of(getters), List.of(names.split(";"))));
>>      }
>>  }
> 



More information about the amber-dev mailing list