RFR: 8152641: Plugin to generate BMH$Species classes ahead-of-time

Peter Levart peter.levart at gmail.com
Thu Mar 31 06:55:05 UTC 2016


Hi Claes,

On 03/30/2016 06:17 PM, Claes Redestad wrote:
> Hi Peter,
>
> something like this, then:
>
> http://cr.openjdk.java.net/~redestad/8152641/webrev.05/
>

Perfect.

> - Added a method only used by the plugin which validates input; added 
> a comment about the dependency
> - Invalid types are logged but otherwise ignored now (I bet someone 
> might suggest a better way to handle user errors)

What happens if you throw an exception in the plugin? Should jlink fail 
in case of invalid input?

Regards, Peter

> - Some cleanup, introduced constant for class name prefix and removed 
> duplicate type string shortening etc
>
> /Claes
>
> On 2016-03-30 17:17, Peter Levart wrote:
>> Hi Claes,
>>
>> webrev.04 looks good now.
>>
>> Maybe just one nit. For production quality, plugin input could be 
>> verified that after expansion it is composed of just the following 
>> characters: "LIJFD". Otherwise ClassWriter might generate an unusable 
>> class without complaining (for example if someone sneaks-in 
>> characters 'S' 'Z' 'B' or 'C')...
>>
>> Or, better yet, create another method in BMH that will be the 
>> "public" API between the plugin and BMH which does the validation and 
>> calls generateConcreteBMHClassBytes(). Internally in BMH the 
>> validation is not necessary as the types strings are composed 
>> programmatically and are guaranteed to be correct.
>>
>> Regards, Peter
>>
>> On 03/30/2016 04:15 PM, Claes Redestad wrote:
>>>
>>>
>>> On 2016-03-30 14:21, Peter Levart wrote:
>>>> Hi Claes,
>>>>
>>>> On 03/30/2016 12:53 PM, Claes Redestad wrote:
>>>>> Hi,
>>>>>
>>>>> I think Class.forName(Module, String) seemed like a nice 
>>>>> efficiency/simplicity compromise, but there is some usage of 
>>>>> lambdas/method-refs in the Module lookup code, especially for 
>>>>> exploded modules (which get exercised during JDK build). Depending 
>>>>> on a lambda from code in java.lang.invoke means we fail to bootstrap.
>>>>>
>>>>> But hey, we're living in an encapsulated world now, and this is in 
>>>>> java.base, so why not go directly to the BootLoader:
>>>>>
>>>>> http://cr.openjdk.java.net/~redestad/8152641/webrev.03/
>>>>>
>>>>> Since this is what's called from Class.forName(Module, String), 
>>>>> the overhead should be even smaller than in your 
>>>>> classForNameInModule test.
>>>>
>>>> Good idea.
>>>>
>>>>>
>>>>> If I call this final, will you say "Reviewed"? :-)
>>>>
>>>> Sorry, I don't posses the powers. :-)
>>>>
>>>> I could say "rEVIEWED", but...
>>>>
>>>> In the plugin, the input is shortened speciesTypes strings. What 
>>>> guarantees that they really are normalized? For example, If one 
>>>> specifies "LLL" as input, it will get expanded into "LLL", the 
>>>> generated class will have "_L3" as a name suffix, but you will pack 
>>>> it in the image with "_LLL.class" filename suffix.
>>>>
>>>> That's another reason why a method in BoundMethodHandle$Factory 
>>>> with the following signature:
>>>>
>>>> Map.Entry<String, byte[]> generateConcreteBMHClassBytes(String types);
>>>>
>>>> ...would be a good idea. It would return class bytes and the name 
>>>> of the class which you could use to derive the class filename 
>>>> without hardcoding the same logic in plugin and in BMH.
>>>>
>>>> You just move the "LambdaForm.shortenSignature(types)" from 
>>>> getConcreteBMHClass and className/sourceFile calculation from 
>>>> generateConcreteBMHClass down to generateConcreteBMHClassBytes 
>>>> method and change the signatures...
>>>
>>> Yes, it makes sense to keep control over the class name inside the 
>>> factory class, and this does allow specifying shortened or expanded 
>>> forms (L3 vs LLL) interchangeably as input to the plugin, which 
>>> reduces possibility for user errors.
>>>
>>> How about this: http://cr.openjdk.java.net/~redestad/8152641/webrev.04/
>>>
>>> /Claes
>>>
>>>>
>>>> Regards, Peter
>>>>
>>>>>
>>>>> /Claes
>>>>>
>>>>> PS: The default list of types is generated in a few adhoc tests 
>>>>> not part of this patch. I'm considering proposing add support for 
>>>>> generating this list at build time. Maybe a JEP called "Build 
>>>>> system support for profile-guided optimizations", which could also 
>>>>> handle https://bugs.openjdk.java.net/browse/JDK-8150044
>>>>>
>>>>> On 2016-03-30 09:53, Peter Levart wrote:
>>>>>> Hi Claes,
>>>>>>
>>>>>> Sorry, I found a flaw in the benchmark (the regex pattern to 
>>>>>> split comma-separated string into substrings was wrong). What the 
>>>>>> benchmark did was compare the overheads of a single lookup of a 
>>>>>> longer class name containing commas. Here's the corrected result 
>>>>>> of overheads of 5 unsuccessful lookups:
>>>>>>
>>>>>> Benchmark (generate)                       (lookup)  Mode 
>>>>>> Cnt      Score     Error  Units
>>>>>> ClassForNameBench.classForName LL,LLL,LLLL,LLLLL,LLLLLL  
>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI avgt   10  29627.141 ± 567.427  ns/op
>>>>>> ClassForNameBench.classForNameInModule LL,LLL,LLLL,LLLLL,LLLLLL  
>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI avgt   10   1073.256 ±  23.794  ns/op
>>>>>> ClassForNameBench.hashSetContains LL,LLL,LLLL,LLLLL,LLLLLL  
>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI avgt   10     33.022 ±   0.066  ns/op
>>>>>> ClassForNameBench.switchStatement LL,LLL,LLLL,LLLLL,LLLLLL  
>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI avgt   10     38.498 ±   5.842  ns/op
>>>>>>
>>>>>> ...overheads are a little bigger (x5 approx.).
>>>>>>
>>>>>>
>>>>>> Here's the corrected benchmark:
>>>>>>
>>>>>>
>>>>>> package jdk.test;
>>>>>>
>>>>>> import org.openjdk.jmh.annotations.*;
>>>>>> import org.openjdk.jmh.infra.Blackhole;
>>>>>>
>>>>>> import java.io.Serializable;
>>>>>> import java.lang.invoke.MethodHandle;
>>>>>> import java.util.Iterator;
>>>>>> import java.util.Set;
>>>>>> import java.util.concurrent.TimeUnit;
>>>>>> import java.util.stream.Collectors;
>>>>>> import java.util.stream.Stream;
>>>>>>
>>>>>> @BenchmarkMode(Mode.AverageTime)
>>>>>> @Fork(value = 1, warmups = 0)
>>>>>> @Warmup(iterations = 10)
>>>>>> @Measurement(iterations = 10)
>>>>>> @OutputTimeUnit(TimeUnit.NANOSECONDS)
>>>>>> @State(Scope.Thread)
>>>>>> public class ClassForNameBench {
>>>>>>
>>>>>>     static final String BMH = "java/lang/invoke/BoundMethodHandle";
>>>>>>     static final String SPECIES_PREFIX_NAME = "Species_";
>>>>>>     static final String SPECIES_PREFIX_PATH = BMH + "$" + 
>>>>>> SPECIES_PREFIX_NAME;
>>>>>>
>>>>>>     @Param({"LL,LLL,LLLL,LLLLL,LLLLLL"})
>>>>>>     public String generate;
>>>>>>
>>>>>>     @Param({"LLI,LLLI,LLLLI,LLLLLI,LLLLLLI"})
>>>>>>     public String lookup;
>>>>>>
>>>>>>     private String[] generatedTypes;
>>>>>>     private String[] lookedUpTypes;
>>>>>>     private Set<String> generatedNames;
>>>>>>     private String[] lookedUpNames;
>>>>>>
>>>>>>     @Setup(Level.Trial)
>>>>>>     public void setup() {
>>>>>>         generatedTypes = generate.trim().split("\\s*,\\s*");
>>>>>>         lookedUpTypes = lookup.trim().split("\\s*,\\s*");
>>>>>>         generatedNames = Stream.of(generatedTypes)
>>>>>>                                .map(types -> SPECIES_PREFIX_PATH 
>>>>>> + shortenSignature(types))
>>>>>> .collect(Collectors.toSet());
>>>>>>         lookedUpNames = Stream.of(lookedUpTypes)
>>>>>>                               .map(types -> SPECIES_PREFIX_PATH + 
>>>>>> shortenSignature(types))
>>>>>>                               .toArray(String[]::new);
>>>>>>     }
>>>>>>
>>>>>>     @Benchmark
>>>>>>     public void classForName(Blackhole bh) {
>>>>>>         for (String name : lookedUpNames) {
>>>>>>             try {
>>>>>>                 bh.consume(Class.forName(name, false, 
>>>>>> MethodHandle.class.getClassLoader()));
>>>>>>             } catch (ClassNotFoundException e) {
>>>>>>                 bh.consume(e);
>>>>>>             }
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>>     @Benchmark
>>>>>>     public void classForNameInModule(Blackhole bh) {
>>>>>>         for (String name : lookedUpNames) {
>>>>>> bh.consume(Class.forName(MethodHandle.class.getModule(), name));
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>>     @Benchmark
>>>>>>     public void hashSetContains(Blackhole bh) {
>>>>>>         for (String name : lookedUpNames) {
>>>>>>             bh.consume(generatedNames.contains(name));
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>>     @Benchmark
>>>>>>     public void switchStatement(Blackhole bh) {
>>>>>>         for (String types : lookedUpTypes) {
>>>>>>             bh.consume(getBMHSwitch(types));
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>>     static Class<?> getBMHSwitch(String types) {
>>>>>>         // should be in sync with @Param generate above...
>>>>>>         switch (types) {
>>>>>>             case "LL": return Object.class;
>>>>>>             case "LLL": return Serializable.class;
>>>>>>             case "LLLL": return Iterator.class;
>>>>>>             case "LLLLL": return Throwable.class;
>>>>>>             case "LLLLLL": return String.class;
>>>>>>             default: return null;
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>>     // copied from non-public LambdaForm
>>>>>>     static String shortenSignature(String signature) {
>>>>>>         // Hack to make signatures more readable when they show 
>>>>>> up in method names.
>>>>>>         final int NO_CHAR = -1, MIN_RUN = 3;
>>>>>>         int c0, c1 = NO_CHAR, c1reps = 0;
>>>>>>         StringBuilder buf = null;
>>>>>>         int len = signature.length();
>>>>>>         if (len < MIN_RUN) return signature;
>>>>>>         for (int i = 0; i <= len; i++) {
>>>>>>             // shift in the next char:
>>>>>>             c0 = c1; c1 = (i == len ? NO_CHAR : signature.charAt(i));
>>>>>>             if (c1 == c0) { ++c1reps; continue; }
>>>>>>             // shift in the next count:
>>>>>>             int c0reps = c1reps; c1reps = 1;
>>>>>>             // end of a  character run
>>>>>>             if (c0reps < MIN_RUN) {
>>>>>>                 if (buf != null) {
>>>>>>                     while (--c0reps >= 0)
>>>>>>                         buf.append((char) c0);
>>>>>>                 }
>>>>>>                 continue;
>>>>>>             }
>>>>>>             // found three or more in a row
>>>>>>             if (buf == null)
>>>>>>                 buf = new StringBuilder().append(signature, 0, i 
>>>>>> - c0reps);
>>>>>>             buf.append((char) c0).append(c0reps);
>>>>>>         }
>>>>>>         return (buf == null) ? signature : buf.toString();
>>>>>>     }
>>>>>> }
>>>>>>
>>>>>>
>>>>>> Regards, Peter
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 03/30/2016 09:40 AM, Peter Levart wrote:
>>>>>>> Hi Claes,
>>>>>>>
>>>>>>> On 03/30/2016 01:03 AM, Claes Redestad wrote:
>>>>>>>> Hi Peter, Mandy,
>>>>>>>>
>>>>>>>> On 2016-03-26 12:47, Peter Levart wrote:
>>>>>>>>>
>>>>>>>>> Comparing this with proposed code from webrev:
>>>>>>>>>
>>>>>>>>>  493                         try {
>>>>>>>>>  494                             return (Class<? extends 
>>>>>>>>> BoundMethodHandle>)
>>>>>>>>>  495 
>>>>>>>>> Class.forName("java.lang.invoke.BoundMethodHandle$Species_" + 
>>>>>>>>> LambdaForm.shortenSignature(types));
>>>>>>>>>  496                         } catch (ClassNotFoundException 
>>>>>>>>> cnf) {
>>>>>>>>>  497                             // Not pregenerated, generate 
>>>>>>>>> the class
>>>>>>>>>  498                             return 
>>>>>>>>> generateConcreteBMHClass(types);
>>>>>>>>>  499                         }
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> ...note that the function passed to 
>>>>>>>>> CLASS_CACHE.computeIfAbsent is executed just once per distinct 
>>>>>>>>> 'types' argument. Even if you put the generated switch between 
>>>>>>>>> a call to getConcreteBMHClass and CLASS_CACHE.computeIfAbsent, 
>>>>>>>>> getConcreteBMHClass(String types) is executed just once per 
>>>>>>>>> distinct 'types' argument (except in rare occasions when VM 
>>>>>>>>> can not initialize the loaded class).
>>>>>>>>>
>>>>>>>>> In this respect a successful Class.forName() is not any worse 
>>>>>>>>> than static resolving. It's just that unsuccessful 
>>>>>>>>> Class.forName() represents some overhead for classes that are 
>>>>>>>>> not pre-generated. So an alternative way to get rid of that 
>>>>>>>>> overhead is to have a HashSet of 'types' strings for 
>>>>>>>>> pre-generated classes at hand in order to decide whether to 
>>>>>>>>> call Class.forName or generateConcreteBMHClass.
>>>>>>>>>
>>>>>>>>> What's easier to support is another question.
>>>>>>>>>
>>>>>>>>> Regards, Peter
>>>>>>>>
>>>>>>>> to have something to compare with I built a version which, like 
>>>>>>>> you suggest,
>>>>>>>> generates a HashSet<String> with the set of generated classes here:
>>>>>>>>
>>>>>>>> http://cr.openjdk.java.net/~redestad/8152641/webrev.02/
>>>>>>>>
>>>>>>>> This adds a fair bit of complexity to the plugin and requires 
>>>>>>>> we add a nested
>>>>>>>> class in BoundMethodHandle that we can replace. Using the 
>>>>>>>> anonymous
>>>>>>>> compute Function for this seems like the best choice for this.
>>>>>>>
>>>>>>> ...what I had in mind as alternative to a pregenerated class 
>>>>>>> with a switch was a simple textual resource file, generated by 
>>>>>>> plugin, read-in by BMH into a HashSet. No special-purpose class 
>>>>>>> generation and much less complexity for the plugin.
>>>>>>>
>>>>>>>>
>>>>>>>> I've not been able to measure any statistical difference in 
>>>>>>>> real startup terms,
>>>>>>>> though, and not figured out a smart way to benchmark the 
>>>>>>>> overhead of the
>>>>>>>> CNFE in relation to the class generation (my guess it adds a 
>>>>>>>> fraction to the
>>>>>>>> total cost) and since this adds ever so little footprint and an 
>>>>>>>> extra lookup to the
>>>>>>>> fast path it would seem that this is the wrong trade-off to do 
>>>>>>>> here.
>>>>>>>
>>>>>>> Yes, perhaps it would be best to just use Class.forName(module, 
>>>>>>> className) instead. I have created a little benchmark to compare 
>>>>>>> overheads (just overheads) of unsuccessful lookups for 
>>>>>>> pregenerated classes (a situation where a BMH class is requested 
>>>>>>> that has not been pregenerated) and here's the result for 
>>>>>>> overhead of 5 unsuccessfull lookups:
>>>>>>>
>>>>>>> Benchmark (generate)                       (lookup)  Mode 
>>>>>>> Cnt     Score     Error  Units
>>>>>>> ClassForNameBench.classForName LL,LLL,LLLL,LLLLL,LLLLLL 
>>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI  avgt   10  6800.878 ± 421.424  ns/op
>>>>>>> ClassForNameBench.classForNameInModule LL,LLL,LLLL,LLLLL,LLLLLL 
>>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI  avgt   10   209.574 ± 2.114  ns/op
>>>>>>> ClassForNameBench.hashSetContains LL,LLL,LLLL,LLLLL,LLLLLL 
>>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI  avgt   10     6.813 ± 0.317  ns/op
>>>>>>> ClassForNameBench.switchStatement LL,LLL,LLLL,LLLLL,LLLLLL 
>>>>>>> LLI,LLLI,LLLLI,LLLLLI,LLLLLLI  avgt   10     6.601 ± 0.061  ns/op
>>>>>>>
>>>>>>> ...compared to runtime BMH class generation and loading this is 
>>>>>>> really a very minor overhead. I would just use 
>>>>>>> Class.forName(module, className) and reduce the complexity of 
>>>>>>> the plugin.
>>>>>>>
>>>>>>> What do you think?
>>>>>>>
>>>>>>>
>>>>>>> Here's the benchmark:
>>>>>>>
>>>>>>> package jdk.test;
>>>>>>>
>>>>>>> import org.openjdk.jmh.annotations.*;
>>>>>>> import org.openjdk.jmh.infra.Blackhole;
>>>>>>>
>>>>>>> import java.io.Serializable;
>>>>>>> import java.lang.invoke.MethodHandle;
>>>>>>> import java.util.Iterator;
>>>>>>> import java.util.Set;
>>>>>>> import java.util.concurrent.TimeUnit;
>>>>>>> import java.util.stream.Collectors;
>>>>>>> import java.util.stream.Stream;
>>>>>>>
>>>>>>> @BenchmarkMode(Mode.AverageTime)
>>>>>>> @Fork(value = 1, warmups = 0)
>>>>>>> @Warmup(iterations = 10)
>>>>>>> @Measurement(iterations = 10)
>>>>>>> @OutputTimeUnit(TimeUnit.NANOSECONDS)
>>>>>>> @State(Scope.Thread)
>>>>>>> public class ClassForNameBench {
>>>>>>>
>>>>>>>     static final String BMH = "java/lang/invoke/BoundMethodHandle";
>>>>>>>     static final String SPECIES_PREFIX_NAME = "Species_";
>>>>>>>     static final String SPECIES_PREFIX_PATH = BMH + "$" + 
>>>>>>> SPECIES_PREFIX_NAME;
>>>>>>>
>>>>>>>     @Param({"LL,LLL,LLLL,LLLLL,LLLLLL"})
>>>>>>>     public String generate;
>>>>>>>
>>>>>>>     @Param({"LLI,LLLI,LLLLI,LLLLLI,LLLLLLI"})
>>>>>>>     public String lookup;
>>>>>>>
>>>>>>>     private String[] generatedTypes;
>>>>>>>     private String[] lookedUpTypes;
>>>>>>>     private Set<String> generatedNames;
>>>>>>>     private String[] lookedUpNames;
>>>>>>>
>>>>>>>     @Setup(Level.Trial)
>>>>>>>     public void setup() {
>>>>>>>         generatedTypes = generate.trim().split("\\s+,\\s+");
>>>>>>>         lookedUpTypes = lookup.trim().split("\\s+,\\s+");
>>>>>>>         generatedNames = Stream.of(generatedTypes)
>>>>>>>                                .map(types -> SPECIES_PREFIX_PATH 
>>>>>>> + shortenSignature(types))
>>>>>>> .collect(Collectors.toSet());
>>>>>>>         lookedUpNames = Stream.of(lookedUpTypes)
>>>>>>>                               .map(types -> SPECIES_PREFIX_PATH 
>>>>>>> + shortenSignature(types))
>>>>>>>                               .toArray(String[]::new);
>>>>>>>     }
>>>>>>>
>>>>>>>     @Benchmark
>>>>>>>     public void classForName(Blackhole bh) {
>>>>>>>         for (String name : lookedUpNames) {
>>>>>>>             try {
>>>>>>>                 bh.consume(Class.forName(name, false, 
>>>>>>> MethodHandle.class.getClassLoader()));
>>>>>>>             } catch (ClassNotFoundException e) {
>>>>>>>                 bh.consume(e);
>>>>>>>             }
>>>>>>>         }
>>>>>>>     }
>>>>>>>
>>>>>>>     @Benchmark
>>>>>>>     public void classForNameInModule(Blackhole bh) {
>>>>>>>         for (String name : lookedUpNames) {
>>>>>>> bh.consume(Class.forName(MethodHandle.class.getModule(), name));
>>>>>>>         }
>>>>>>>     }
>>>>>>>
>>>>>>>     @Benchmark
>>>>>>>     public void hashSetContains(Blackhole bh) {
>>>>>>>         for (String name : lookedUpNames) {
>>>>>>>             bh.consume(generatedNames.contains(name));
>>>>>>>         }
>>>>>>>     }
>>>>>>>
>>>>>>>     @Benchmark
>>>>>>>     public void switchStatement(Blackhole bh) {
>>>>>>>         for (String types : lookedUpTypes) {
>>>>>>>             bh.consume(getBMHSwitch(types));
>>>>>>>         }
>>>>>>>     }
>>>>>>>
>>>>>>>     static Class<?> getBMHSwitch(String types) {
>>>>>>>         // should be in sync with @Param generate above...
>>>>>>>         switch (types) {
>>>>>>>             case "LL": return Object.class;
>>>>>>>             case "LLL": return Serializable.class;
>>>>>>>             case "LLLL": return Iterator.class;
>>>>>>>             case "LLLLL": return Throwable.class;
>>>>>>>             case "LLLLLL": return String.class;
>>>>>>>             default: return null;
>>>>>>>         }
>>>>>>>     }
>>>>>>>
>>>>>>>     // copied from non-public LambdaForm
>>>>>>>     static String shortenSignature(String signature) {
>>>>>>>         // Hack to make signatures more readable when they show 
>>>>>>> up in method names.
>>>>>>>         final int NO_CHAR = -1, MIN_RUN = 3;
>>>>>>>         int c0, c1 = NO_CHAR, c1reps = 0;
>>>>>>>         StringBuilder buf = null;
>>>>>>>         int len = signature.length();
>>>>>>>         if (len < MIN_RUN) return signature;
>>>>>>>         for (int i = 0; i <= len; i++) {
>>>>>>>             // shift in the next char:
>>>>>>>             c0 = c1; c1 = (i == len ? NO_CHAR : 
>>>>>>> signature.charAt(i));
>>>>>>>             if (c1 == c0) { ++c1reps; continue; }
>>>>>>>             // shift in the next count:
>>>>>>>             int c0reps = c1reps; c1reps = 1;
>>>>>>>             // end of a  character run
>>>>>>>             if (c0reps < MIN_RUN) {
>>>>>>>                 if (buf != null) {
>>>>>>>                     while (--c0reps >= 0)
>>>>>>>                         buf.append((char) c0);
>>>>>>>                 }
>>>>>>>                 continue;
>>>>>>>             }
>>>>>>>             // found three or more in a row
>>>>>>>             if (buf == null)
>>>>>>>                 buf = new StringBuilder().append(signature, 0, i 
>>>>>>> - c0reps);
>>>>>>>             buf.append((char) c0).append(c0reps);
>>>>>>>         }
>>>>>>>         return (buf == null) ? signature : buf.toString();
>>>>>>>     }
>>>>>>> }
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Regards, Peter
>>>>>>>
>>>>>>>>
>>>>>>>> All-in-all I lean towards moving forward with the first, 
>>>>>>>> simpler revision of this
>>>>>>>> patch[1] and then evaluate if webrev.02 or a 
>>>>>>>> String-switch+static resolve
>>>>>>>> as a follow-up.
>>>>>>>>
>>>>>>>> A compromise would be to keep the SpeciesLookup class 
>>>>>>>> introduced here
>>>>>>>> to allow removing all overhead in case the plugin is disabled.
>>>>>>>>
>>>>>>>> Mandy: I've not found any test (under jdk/test/tools/jlink or 
>>>>>>>> elsewhere) which
>>>>>>>> has to be updated when adding a plugin like this.
>>>>>>>>
>>>>>>>> Thanks!
>>>>>>>>
>>>>>>>> /Claes
>>>>>>>>
>>>>>>>> [1] http://cr.openjdk.java.net/~redestad/8152641/webrev.01/ 
>>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>



More information about the jigsaw-dev mailing list