src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java
Index Unified diffs Context diffs Sdiffs Patch New Old Previous File Next File jdk Sdiff src/java.base/share/classes/java/lang/invoke

src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java

Print this page
rev 11011 : 8057020: LambdaForm caches should support eviction
Reviewed-by: psandoz, ?


   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 

  28 import java.util.Arrays;
  29 import static java.lang.invoke.LambdaForm.*;
  30 import static java.lang.invoke.LambdaForm.BasicType.*;
  31 import static java.lang.invoke.MethodHandleImpl.Intrinsic;
  32 import java.util.Collections;
  33 import java.util.concurrent.ConcurrentHashMap;
  34 
  35 import sun.invoke.util.Wrapper;
  36 
  37 /** Transforms on LFs.
  38  *  A lambda-form editor can derive new LFs from its base LF.
  39  *  The editor can cache derived LFs, which simplifies the reuse of their underlying bytecodes.
  40  *  To support this caching, a LF has an optional pointer to its editor.
  41  */
  42 class LambdaFormEditor {
  43     final LambdaForm lambdaForm;
  44 
  45     private LambdaFormEditor(LambdaForm lambdaForm) {
  46         this.lambdaForm = lambdaForm;
  47     }
  48 
  49     // Factory method.
  50     static LambdaFormEditor lambdaFormEditor(LambdaForm lambdaForm) {
  51         // TO DO:  Consider placing intern logic here, to cut down on duplication.
  52         // lambdaForm = findPreexistingEquivalent(lambdaForm)
  53         return new LambdaFormEditor(lambdaForm);
  54     }
  55 
  56     /** A description of a cached transform, possibly associated with the result of the transform.
  57      *  The logical content is a sequence of byte values, starting with a Kind.ordinal value.
  58      *  The sequence is unterminated, ending with an indefinite number of zero bytes.
  59      *  Sequences that are simple (short enough and with small enough values) pack into a 64-bit long.
  60      */
  61     private static final class Transform {
  62         final long packedBytes;
  63         final byte[] fullBytes;
  64         final LambdaForm result;  // result of transform, or null, if there is none available
  65 
  66         private enum Kind {
  67             NO_KIND,  // necessary because ordinal must be greater than zero
  68             BIND_ARG, ADD_ARG, DUP_ARG,
  69             SPREAD_ARGS,
  70             FILTER_ARG, FILTER_RETURN, FILTER_RETURN_TO_ZERO,
  71             COLLECT_ARGS, COLLECT_ARGS_TO_VOID, COLLECT_ARGS_TO_ARRAY,
  72             FOLD_ARGS, FOLD_ARGS_TO_VOID,
  73             PERMUTE_ARGS
  74             //maybe add more for guard with test, catch exception, pointwise type conversions
  75         }
  76 
  77         private static final boolean STRESS_TEST = false; // turn on to disable most packing
  78         private static final int
  79                 PACKED_BYTE_SIZE = (STRESS_TEST ? 2 : 4),
  80                 PACKED_BYTE_MASK = (1 << PACKED_BYTE_SIZE) - 1,
  81                 PACKED_BYTE_MAX_LENGTH = (STRESS_TEST ? 3 : 64 / PACKED_BYTE_SIZE);
  82 
  83         private static long packedBytes(byte[] bytes) {
  84             if (bytes.length > PACKED_BYTE_MAX_LENGTH)  return 0;


 123             }
 124             assert(packedBytes(bytes) == 0);
 125             return bytes;
 126         }
 127 
 128         private byte byteAt(int i) {
 129             long pb = packedBytes;
 130             if (pb == 0) {
 131                 if (i >= fullBytes.length)  return 0;
 132                 return fullBytes[i];
 133             }
 134             assert(fullBytes == null);
 135             if (i > PACKED_BYTE_MAX_LENGTH)  return 0;
 136             int pos = (i * PACKED_BYTE_SIZE);
 137             return (byte)((pb >>> pos) & PACKED_BYTE_MASK);
 138         }
 139 
 140         Kind kind() { return Kind.values()[byteAt(0)]; }
 141 
 142         private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {

 143             this.packedBytes = packedBytes;
 144             this.fullBytes = fullBytes;
 145             this.result = result;
 146         }
 147         private Transform(long packedBytes) {
 148             this(packedBytes, null, null);
 149             assert(packedBytes != 0);
 150         }
 151         private Transform(byte[] fullBytes) {
 152             this(0, fullBytes, null);
 153         }
 154 
 155         private static byte bval(int b) {
 156             assert((b & 0xFF) == b);  // incoming value must fit in *unsigned* byte
 157             return (byte)b;
 158         }
 159         private static byte bval(Kind k) {
 160             return bval(k.ordinal());
 161         }
 162         static Transform of(Kind k, int b1) {
 163             byte b0 = bval(k);
 164             if (inRange(b0 | b1))
 165                 return new Transform(packedBytes(b0, b1));


 226             }
 227             return Arrays.hashCode(fullBytes);
 228         }
 229         @Override
 230         public String toString() {
 231             StringBuilder buf = new StringBuilder();
 232             long bits = packedBytes;
 233             if (bits != 0) {
 234                 buf.append("(");
 235                 while (bits != 0) {
 236                     buf.append(bits & PACKED_BYTE_MASK);
 237                     bits >>>= PACKED_BYTE_SIZE;
 238                     if (bits != 0)  buf.append(",");
 239                 }
 240                 buf.append(")");
 241             }
 242             if (fullBytes != null) {
 243                 buf.append("unpacked");
 244                 buf.append(Arrays.toString(fullBytes));
 245             }

 246             if (result != null) {
 247                 buf.append(" result=");
 248                 buf.append(result);
 249             }
 250             return buf.toString();
 251         }
 252     }
 253 
 254     /** Find a previously cached transform equivalent to the given one, and return its result. */
 255     private LambdaForm getInCache(Transform key) {
 256         assert(key.result == null);
 257         // The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap.
 258         Object c = lambdaForm.transformCache;
 259         Transform k = null;
 260         if (c instanceof ConcurrentHashMap) {
 261             @SuppressWarnings("unchecked")
 262             ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
 263             k = m.get(key);
 264         } else if (c == null) {
 265             return null;
 266         } else if (c instanceof Transform) {
 267             // one-element cache avoids overhead of an array
 268             Transform t = (Transform)c;
 269             if (t.equals(key))  k = t;
 270         } else {
 271             Transform[] ta = (Transform[])c;
 272             for (int i = 0; i < ta.length; i++) {
 273                 Transform t = ta[i];
 274                 if (t == null)  break;
 275                 if (t.equals(key)) { k = t; break; }
 276             }
 277         }
 278         assert(k == null || key.equals(k));
 279         return k == null ? null : k.result;
 280     }
 281 
 282     /** Arbitrary but reasonable limits on Transform[] size for cache. */
 283     private static final int MIN_CACHE_ARRAY_SIZE = 4, MAX_CACHE_ARRAY_SIZE = 16;
 284 
 285     /** Cache a transform with its result, and return that result.
 286      *  But if an equivalent transform has already been cached, return its result instead.
 287      */
 288     private LambdaForm putInCache(Transform key, LambdaForm form) {
 289         key = key.withResult(form);
 290         for (int pass = 0; ; pass++) {
 291             Object c = lambdaForm.transformCache;
 292             if (c instanceof ConcurrentHashMap) {
 293                 @SuppressWarnings("unchecked")
 294                 ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
 295                 Transform k = m.putIfAbsent(key, key);
 296                 return k != null ? k.result : form;










 297             }
 298             assert(pass == 0);
 299             synchronized (lambdaForm) {
 300                 c = lambdaForm.transformCache;
 301                 if (c instanceof ConcurrentHashMap)
 302                     continue;
 303                 if (c == null) {
 304                     lambdaForm.transformCache = key;
 305                     return form;
 306                 }
 307                 Transform[] ta;
 308                 if (c instanceof Transform) {
 309                     Transform k = (Transform)c;
 310                     if (k.equals(key)) {
 311                         return k.result;









 312                     }
 313                     // expand one-element cache to small array
 314                     ta = new Transform[MIN_CACHE_ARRAY_SIZE];
 315                     ta[0] = k;
 316                     lambdaForm.transformCache = c = ta;
 317                 } else {
 318                     // it is already expanded
 319                     ta = (Transform[])c;
 320                 }
 321                 int len = ta.length;

 322                 int i;
 323                 for (i = 0; i < len; i++) {
 324                     Transform k = ta[i];
 325                     if (k == null) {
 326                         break;
 327                     }
 328                     if (k.equals(key)) {
 329                         return k.result;





 330                     }


 331                 }
 332                 if (i < len) {

 333                     // just fall through to cache update
 334                 } else if (len < MAX_CACHE_ARRAY_SIZE) {
 335                     len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE);
 336                     ta = Arrays.copyOf(ta, len);
 337                     lambdaForm.transformCache = ta;
 338                 } else {
 339                     ConcurrentHashMap<Transform, Transform> m = new ConcurrentHashMap<>(MAX_CACHE_ARRAY_SIZE * 2);
 340                     for (Transform k : ta) {
 341                         m.put(k, k);
 342                     }
 343                     lambdaForm.transformCache = m;
 344                     // The second iteration will update for this query, concurrently.
 345                     continue;
 346                 }
 347                 ta[i] = key;

 348                 return form;
 349             }
 350         }
 351     }
 352 
 353     private LambdaFormBuffer buffer() {
 354         return new LambdaFormBuffer(lambdaForm);
 355     }
 356 
 357     /// Editing methods for method handles.  These need to have fast paths.
 358 
 359     private BoundMethodHandle.SpeciesData oldSpeciesData() {
 360         return BoundMethodHandle.speciesData(lambdaForm);
 361     }
 362     private BoundMethodHandle.SpeciesData newSpeciesData(BasicType type) {
 363         return oldSpeciesData().extendWith(type);
 364     }
 365 
 366     BoundMethodHandle bindArgumentL(BoundMethodHandle mh, int pos, Object value) {
 367         assert(mh.speciesData() == oldSpeciesData());




   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import java.lang.ref.SoftReference;
  29 import java.util.Arrays;
  30 import static java.lang.invoke.LambdaForm.*;
  31 import static java.lang.invoke.LambdaForm.BasicType.*;
  32 import static java.lang.invoke.MethodHandleImpl.Intrinsic;
  33 import java.util.Collections;
  34 import java.util.concurrent.ConcurrentHashMap;
  35 
  36 import sun.invoke.util.Wrapper;
  37 
  38 /** Transforms on LFs.
  39  *  A lambda-form editor can derive new LFs from its base LF.
  40  *  The editor can cache derived LFs, which simplifies the reuse of their underlying bytecodes.
  41  *  To support this caching, a LF has an optional pointer to its editor.
  42  */
  43 class LambdaFormEditor {
  44     final LambdaForm lambdaForm;
  45 
  46     private LambdaFormEditor(LambdaForm lambdaForm) {
  47         this.lambdaForm = lambdaForm;
  48     }
  49 
  50     // Factory method.
  51     static LambdaFormEditor lambdaFormEditor(LambdaForm lambdaForm) {
  52         // TO DO:  Consider placing intern logic here, to cut down on duplication.
  53         // lambdaForm = findPreexistingEquivalent(lambdaForm)
  54         return new LambdaFormEditor(lambdaForm);
  55     }
  56 
  57     /** A description of a cached transform, possibly associated with the result of the transform.
  58      *  The logical content is a sequence of byte values, starting with a Kind.ordinal value.
  59      *  The sequence is unterminated, ending with an indefinite number of zero bytes.
  60      *  Sequences that are simple (short enough and with small enough values) pack into a 64-bit long.
  61      */
  62     private static final class Transform extends SoftReference<LambdaForm> {
  63         final long packedBytes;
  64         final byte[] fullBytes;

  65 
  66         private enum Kind {
  67             NO_KIND,  // necessary because ordinal must be greater than zero
  68             BIND_ARG, ADD_ARG, DUP_ARG,
  69             SPREAD_ARGS,
  70             FILTER_ARG, FILTER_RETURN, FILTER_RETURN_TO_ZERO,
  71             COLLECT_ARGS, COLLECT_ARGS_TO_VOID, COLLECT_ARGS_TO_ARRAY,
  72             FOLD_ARGS, FOLD_ARGS_TO_VOID,
  73             PERMUTE_ARGS
  74             //maybe add more for guard with test, catch exception, pointwise type conversions
  75         }
  76 
  77         private static final boolean STRESS_TEST = false; // turn on to disable most packing
  78         private static final int
  79                 PACKED_BYTE_SIZE = (STRESS_TEST ? 2 : 4),
  80                 PACKED_BYTE_MASK = (1 << PACKED_BYTE_SIZE) - 1,
  81                 PACKED_BYTE_MAX_LENGTH = (STRESS_TEST ? 3 : 64 / PACKED_BYTE_SIZE);
  82 
  83         private static long packedBytes(byte[] bytes) {
  84             if (bytes.length > PACKED_BYTE_MAX_LENGTH)  return 0;


 123             }
 124             assert(packedBytes(bytes) == 0);
 125             return bytes;
 126         }
 127 
 128         private byte byteAt(int i) {
 129             long pb = packedBytes;
 130             if (pb == 0) {
 131                 if (i >= fullBytes.length)  return 0;
 132                 return fullBytes[i];
 133             }
 134             assert(fullBytes == null);
 135             if (i > PACKED_BYTE_MAX_LENGTH)  return 0;
 136             int pos = (i * PACKED_BYTE_SIZE);
 137             return (byte)((pb >>> pos) & PACKED_BYTE_MASK);
 138         }
 139 
 140         Kind kind() { return Kind.values()[byteAt(0)]; }
 141 
 142         private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
 143             super(result);
 144             this.packedBytes = packedBytes;
 145             this.fullBytes = fullBytes;

 146         }
 147         private Transform(long packedBytes) {
 148             this(packedBytes, null, null);
 149             assert(packedBytes != 0);
 150         }
 151         private Transform(byte[] fullBytes) {
 152             this(0, fullBytes, null);
 153         }
 154 
 155         private static byte bval(int b) {
 156             assert((b & 0xFF) == b);  // incoming value must fit in *unsigned* byte
 157             return (byte)b;
 158         }
 159         private static byte bval(Kind k) {
 160             return bval(k.ordinal());
 161         }
 162         static Transform of(Kind k, int b1) {
 163             byte b0 = bval(k);
 164             if (inRange(b0 | b1))
 165                 return new Transform(packedBytes(b0, b1));


 226             }
 227             return Arrays.hashCode(fullBytes);
 228         }
 229         @Override
 230         public String toString() {
 231             StringBuilder buf = new StringBuilder();
 232             long bits = packedBytes;
 233             if (bits != 0) {
 234                 buf.append("(");
 235                 while (bits != 0) {
 236                     buf.append(bits & PACKED_BYTE_MASK);
 237                     bits >>>= PACKED_BYTE_SIZE;
 238                     if (bits != 0)  buf.append(",");
 239                 }
 240                 buf.append(")");
 241             }
 242             if (fullBytes != null) {
 243                 buf.append("unpacked");
 244                 buf.append(Arrays.toString(fullBytes));
 245             }
 246             LambdaForm result = get();
 247             if (result != null) {
 248                 buf.append(" result=");
 249                 buf.append(result);
 250             }
 251             return buf.toString();
 252         }
 253     }
 254 
 255     /** Find a previously cached transform equivalent to the given one, and return its result. */
 256     private LambdaForm getInCache(Transform key) {
 257         assert(key.get() == null);
 258         // The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap.
 259         Object c = lambdaForm.transformCache;
 260         Transform k = null;
 261         if (c instanceof ConcurrentHashMap) {
 262             @SuppressWarnings("unchecked")
 263             ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
 264             k = m.get(key);
 265         } else if (c == null) {
 266             return null;
 267         } else if (c instanceof Transform) {
 268             // one-element cache avoids overhead of an array
 269             Transform t = (Transform)c;
 270             if (t.equals(key))  k = t;
 271         } else {
 272             Transform[] ta = (Transform[])c;
 273             for (int i = 0; i < ta.length; i++) {
 274                 Transform t = ta[i];
 275                 if (t == null)  break;
 276                 if (t.equals(key)) { k = t; break; }
 277             }
 278         }
 279         assert(k == null || key.equals(k));
 280         return (k != null) ? k.get() : null;
 281     }
 282 
 283     /** Arbitrary but reasonable limits on Transform[] size for cache. */
 284     private static final int MIN_CACHE_ARRAY_SIZE = 4, MAX_CACHE_ARRAY_SIZE = 16;
 285 
 286     /** Cache a transform with its result, and return that result.
 287      *  But if an equivalent transform has already been cached, return its result instead.
 288      */
 289     private LambdaForm putInCache(Transform key, LambdaForm form) {
 290         key = key.withResult(form);
 291         for (int pass = 0; ; pass++) {
 292             Object c = lambdaForm.transformCache;
 293             if (c instanceof ConcurrentHashMap) {
 294                 @SuppressWarnings("unchecked")
 295                 ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
 296                 Transform k = m.putIfAbsent(key, key);
 297                 if (k == null) return form;
 298                 LambdaForm result = k.get();
 299                 if (result != null) {
 300                     return result;
 301                 } else {
 302                     if (m.replace(key, k, key)) {
 303                         return form;
 304                     } else {
 305                         continue;
 306                     }
 307                 }
 308             }
 309             assert(pass == 0);
 310             synchronized (lambdaForm) {
 311                 c = lambdaForm.transformCache;
 312                 if (c instanceof ConcurrentHashMap)
 313                     continue;
 314                 if (c == null) {
 315                     lambdaForm.transformCache = key;
 316                     return form;
 317                 }
 318                 Transform[] ta;
 319                 if (c instanceof Transform) {
 320                     Transform k = (Transform)c;
 321                     if (k.equals(key)) {
 322                         LambdaForm result = k.get();
 323                         if (result == null) {
 324                             lambdaForm.transformCache = key;
 325                             return form;
 326                         } else {
 327                             return result;
 328                         }
 329                     } else if (k.get() == null) { // overwrite stale entry
 330                         lambdaForm.transformCache = key;
 331                         return form;
 332                     }
 333                     // expand one-element cache to small array
 334                     ta = new Transform[MIN_CACHE_ARRAY_SIZE];
 335                     ta[0] = k;
 336                     lambdaForm.transformCache = ta;
 337                 } else {
 338                     // it is already expanded
 339                     ta = (Transform[])c;
 340                 }
 341                 int len = ta.length;
 342                 int stale = -1;
 343                 int i;
 344                 for (i = 0; i < len; i++) {
 345                     Transform k = ta[i];
 346                     if (k == null) {
 347                         break;
 348                     }
 349                     if (k.equals(key)) {
 350                         LambdaForm result = k.get();
 351                         if (result == null) {
 352                             ta[i] = key;
 353                             return form;
 354                         } else {
 355                             return result;
 356                         }
 357                     } else if (stale < 0 && k.get() == null) {
 358                         stale = i; // remember 1st stale entry index
 359                     }
 360                 }
 361                 if (i < len || stale >= 0) {
 362                     // just fall through to cache update
 363                 } else if (len < MAX_CACHE_ARRAY_SIZE) {
 364                     len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE);
 365                     ta = Arrays.copyOf(ta, len);
 366                     lambdaForm.transformCache = ta;
 367                 } else {
 368                     ConcurrentHashMap<Transform, Transform> m = new ConcurrentHashMap<>(MAX_CACHE_ARRAY_SIZE * 2);
 369                     for (Transform k : ta) {
 370                         m.put(k, k);
 371                     }
 372                     lambdaForm.transformCache = m;
 373                     // The second iteration will update for this query, concurrently.
 374                     continue;
 375                 }
 376                 int idx = (stale >= 0) ? stale : i;
 377                 ta[idx] = key;
 378                 return form;
 379             }
 380         }
 381     }
 382 
 383     private LambdaFormBuffer buffer() {
 384         return new LambdaFormBuffer(lambdaForm);
 385     }
 386 
 387     /// Editing methods for method handles.  These need to have fast paths.
 388 
 389     private BoundMethodHandle.SpeciesData oldSpeciesData() {
 390         return BoundMethodHandle.speciesData(lambdaForm);
 391     }
 392     private BoundMethodHandle.SpeciesData newSpeciesData(BasicType type) {
 393         return oldSpeciesData().extendWith(type);
 394     }
 395 
 396     BoundMethodHandle bindArgumentL(BoundMethodHandle mh, int pos, Object value) {
 397         assert(mh.speciesData() == oldSpeciesData());


src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java
Index Unified diffs Context diffs Sdiffs Patch New Old Previous File Next File