Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8303704

Add a dynamically updating MethodHandle combinator that switches MethodHandles

XMLWordPrintable

      Add a dynamically updating MethodHandle combinator that switches MethodHandles

      As a follow-on to JDK-8263087, add a method handle combinator `dynamicSwitch` that performs a dynamic test on an argument, calling a bootstrap method (“BSM”) the first time it sees each distinct argument value, and calling the same method handle every time it sees any given argument value. The method handle (“MH”) returned from the `dynamicSwitch` combinator factory may itself be called a “dynamic switch”.

      Each dynamic switch remembers the BSM it was configured with and uses it to determine behavior. Each call to a dynamic switch extracts a special argument (of that call) called the “selector”. The first time a distinct selector is encountered, the BSM is invoked to determine how to finish the call; this determination takes (at least) the form of a MH that is of the same type as the dynamic switch MH itself. This special MH is called the “case handler” for that selector.

      The selector can be of any type for which `Object::equals` and `Object::hashCode` is useful to the end user. The processing is as if the selector values encountered were stored in a `HashMap`. Perhaps optionally, a different strategy for storing selectors (such as `WeakHashMap`) can be configured in a given call to `dynamicSwitch`.

      The expectation of performance is that if the JVM can determine that a particular selector argument is constant in a particular call to a dynamic switch, and the case handler for that selector is known, the JVM will often substitute the code for the case handler at the call site. (Relative to a weak hash table, the JVM behavior may be as if the compiled code contains an extra reference to the case handler MH, keeping it live, and/or as if there is a race condition where the behavior persists even after the case handler object goes away.)

      The selector must be the first argument to the dynamic switch. Users can dispatch on other arguments by transforming relevant method handles with `permuteArguments` to swing the selector in and out of first position. Perhaps optionally, the selector can be in any argument position, as indicated by an `int pos` argument to `dynamicSwitch`, and this would be much more convenient as well as perhaps more performant.

      The result of each call to the BSM is memoized in the internal state of the dynamic switch. This state is maintained separately in each distinct dynamic switch instance (as produced by distinct calls to `dynamicSwitch`).

      Perhaps optionally, there would be way to avoid adding to the state (for particular calls), by having the BSM produce a slightly different result, such as a “fallback” MH that just handles a particular invocation of the dynamic switch, but without memoization. Perhaps a null selector also implies the fallback. Perhaps optionally, there would be a way to edit the memoized state, so as to remove stale MHs previously installed.

      Users who wish to dispatch on simpler, better-behaved selectors (such as provided by `Object::getClass` or `Object::hashCode` or `Enum::ordinal`) can use `foldArguments` to temporarily simplify key arguments, adding them to the argument list, where they can be dispose of later via `dropArguments`. A simple adjustment to BSM logic can do this. Perhaps optionally, the new API can accept a key-extraction function to be applied to an argument, “as if” the extra argument were added using `foldArguments` but then dropped later; this may perhaps perform better as well as being much more convenient for end-users.

      Argument equivalence is as determined by `Object::equals`, with the possible assistance of `Object::hashCode`. Users who need different comparisons (such as case-folding ones) can get this effect by extracting appropriate keys (such as `String::toLowerCase`). Perhaps optionally, a user-defined comparison function (with associated hash) could be specified instead, and would perform better.

      Multiple logical key arguments can be dispatched on by temporarily collecting them via `List::of` into a single physical key, and dispatching on that single value. Perhaps optionally multiple arguments can be handled directly by the new API, “as if” collected via `List::of` and later dropped with `dropArguments`, but perhaps with better performance.

      Perhaps optionally a list of all possible valid keys could be supplied, allowing the implementation to avoid most of the dynamism, and instead concentrate on efficiently indexing the keys, using (nearly-)perfect hash functions and the like. In taht case, a natural alternative to specifying a BSM would be specifying a map from keys to case handlers, plus a fallback. Such a switch, frozen from the beginning, can be called a “static switch”, although it could share infrastructure with dynamic switches. Perhaps it might make sense, sometimes, to “freeze” a switch which was previously fully dynamic; such a switch would have a BSM, but would never call it after being frozen.

      Perhaps optionally the above behaviors could be packed with a new kind of mutable call site, the `DynamicSwitchCallSite`. Some of the advanced optional features mentioned above, such fallbacks or removing handlers, might be natural to position within such an API.

      Updates to the dynamic switch state are thread-safe, in that even with races, only one BSM result per selector is ever used (until and unless the result is removed, via an optional feature). As is customary with related APIs, multiple invocations of the BSM on some given selector may race to compute handler MHs, and a winner is arbitrarily selected. As is also customary, infinite bootstrap regress (calling the BSM recursively on the same key) is diagnosed with `StackOverflowError`, unless, perhaps, the BSM takes its own measures.

      The simplest possible form of `dynamicSwitch`, therefore, looks like
      something like this, with a pseudocode implementation which is not
      optimized:

      ```
      /**
       * Creates a dynamic switch method handle, which can be used to switch over
       * an open-ended set of target values.
       * The first argument of the type (there must be one) is the selector.
       * Each new selector is mapped to a case handler as if by
       * {@code (MethodHandle)bsm.invokeExact(type, args[0])}.
       */
      MethodHandle dynamicSwitch(MethodHandle fallback, MethodHandle bsm);
      MethodHandle staticSwitch(MethodHandle fallback, Map<S,MethodHandle> cases);
      MethodHandle dynamicSwitch(MethodType type, MethodHandle bsm);

      ```

      See <https://cr.openjdk.org/~jrose/draft/DynamicSwitchPseudo.java> for details.

      More option-rich versions might look like this:

      ```
      // dynamic switch with default (“fallback”) behavior, which also implies type
      // a null selector and/or a null bsm short-circuits to the fallback
      MethodHandle dynamicSwitch(MethodHandle fallback, MethodHandle bsm) {…}

      // initially frozen (“static”) switch, with a fixed set of case handlers
      <S> MethodHandle staticSwitch(MH fb, Map<S,MethodHandle> cases) {…}

      // pos argument specifies where the method handle selector comes from
      MethodHandle dynamicSwitch(MH fb, int pos, MH bsm) {…}
      MethodHandle staticSwitch(MH fb, int pos, Map<S,MH> cases) {…}


      // multiple pos arguments imply multiple keys, collected as if by List::of
      MethodHandle dynamicSwitch(MH fb, int[] poses, MH bsm) {…}
      MethodHandle staticSwitch(MH fb, int[] poses, Map<S,MH> cases) {…}

      // key MH extracts a single selector as if by `foldArguments` (can take >1 args)
      MethodHandle dynamicSwitch(MH fb, int pos, MethodHandle key, MH bsm) {…}
      // multiple extractors, all start at pos=0, collected as if by List::of
      MethodHandle dynamicSwitch(MH fb, MethodHandle[] keys, MH bsm) {…}

      // call site packages up a dynamic switch conveniently and performantly
      public class DynamicSwitchCallSite extends MutableCallSite {
        public DynamicSwitchCallSite(MethodHandle fallback, MethodHandle bsm) {
          super(dynamicSwitch(fallback, bsm));
        }
        public DynamicSwitchCallSite(MethodHandle fallback, MethodHandle bsm, MethodHandle... keys) {
          super(dynamicSwitch(fallback, bsm, keys));
        }

        // virtual methods allow client to customize selector state management

        // check and normalize the selector; return null if it should short-circuit
        protected Object normalizeSelector(Object selector) {
          return selector;
        }
        // view the current state, perhaps modify it
        protected Map<Object,MethodHandle> handlers() {
        }
        // stay with the handlers we have now, forgetting about the BSM
        protected void freezeHandlers(Object selector) {
          …something that clears out pending BSMs and sets a flag…
        }
        // try to forget about any case handler for the given selector
        protected void removeSelector(Object selector) {
          handlers().remove(normalizeSelector(selector));
        }
        // be sure to retain any case handler for the given selector
        protected void retainSelector(Object selector) {
          strongRoots.add(normalizeSelector(selector));
        }
        // map a selector to a unique case number, or return -1 if it is invalid
        protected int indexSelector(Object selector) {
          …something with a weak map or regular map…
        }
      }
      ```

            Unassigned Unassigned
            jrose John Rose
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: