Strategy patterns in indy bootstraps, and testing

Brian Goetz brian.goetz at
Tue Oct 23 17:29:49 UTC 2018

We've seen several examples where indy/condy bootstraps may want to 
employ a pluggable strategy pattern to decide how to link a call site:

  - Lambda Metafactory -- class-per-lambda, class-per-SAM, dynamic 
proxies (hah)
  - Indy String Concat -- see the enum StringConcatFactory.Strategy
  - Switch dispatch -- linear search, decision tree, etc

And we'll see more, as we migrate more functionality into indy-based 
runtime.  It's a reasonable time to start gathering "best practices" for 
writing (and documenting) such things.

String concat makes a good stab at it:

  - Defines a private enum of strategies
  - Pick a default strategy
  - Allow (load-time) override of strategy by reading a system property 
and interpreting it as an enum name
  - For testing, it runs the tests for each known strategy:

  * @run main/othervm -Xverify:all -Djava.lang.invoke.stringConcat=BC_SB
  * @run main/othervm -Xverify:all 
  * @run main/othervm -Xverify:all 
  * @run main/othervm -Xverify:all 
  * @run main/othervm -Xverify:all 
  * @run main/othervm -Xverify:all 

Actually, though, I cheated; because the test space is 6 (strategies) x 
4 (combinations of runtime flags) x 2 (compilation options), this block 
of directives has 48 lines.  Which is thorough and well-factored, but 
seems brittle -- each time you add a new strategy, you have to update 
all the tests under all the combinations.

(Further, metafactories that spin classfiles should provide a "dumper" 
option, like LMF does, for debugging.)

The main tension here is between that of wanting reliable behavior (pick 
a strategy at class load, and stick to it) and wanting good test 
coverage over a range of configurations (can't reload classes in 
java.base just by dumping the class loader.)  I understand why Aleksey 
picked the strategy he did, but this is the usual tension between 
anything-static and testing.

Even if we separate testing of the bootstrap from testing of the 
compilation behavior by writing our unit tests against dynamic invokers 
(which we probably should anyway), we still have the same problem -- on 
first load of the bootstrap class, we have already picked a strategy.

An alternate (ahem) strategy would be to have a public version of the 
bootstrap and a package-private one:

     /* package */ CallSite bsm(Lookup lookup, String name, MethodType 
                                Strategy strategy,
                                BSM_ARGS) { ... the real work ... }

     public Callsite bsm(Lookup lookup, String name, MethodType type,
                         BSM_ARGS) {
         return bsm(lookup, name, type, STRATEGY, BSM_ARGS);

which would allow the BSM to be unit tested directly, with the strategy 
under the control of the test.

Then our testing strategy would consist of:
  - Extensive unit tests for the internal bootstrap, ranging over all 
the possible knobs and bells, using dynamicInvoker or similar
  - A smaller number of integration tests using actual indy (will be 
nice when we have JEP 303)
  - (if needed) end-to-end integration tests including varying 
compilation strategies


More information about the amber-spec-experts mailing list