Strategy patterns in indy bootstraps, and testing
Brian Goetz
brian.goetz at oracle.com
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
-Djava.lang.invoke.stringConcat=BC_SB_SIZED
* @run main/othervm -Xverify:all
-Djava.lang.invoke.stringConcat=MH_SB_SIZED
* @run main/othervm -Xverify:all
-Djava.lang.invoke.stringConcat=BC_SB_SIZED_EXACT
* @run main/othervm -Xverify:all
-Djava.lang.invoke.stringConcat=MH_SB_SIZED_EXACT
* @run main/othervm -Xverify:all
-Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT
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
type,
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
Thoughts?
More information about the amber-spec-experts
mailing list