# HG changeset patch # User vlivanov # Date 1410362392 -14400 # Node ID c5b74a88a3c099f6846afeb02eed46cf62862172 # Parent 92d431f1ec8daf9fb628593dded3979bc6450b5a 8057042: LambdaFormEditor: derive new LFs from a base LF Reviewed-by: vlivanov, psandoz Contributed-by: john.r.rose@oracle.com diff -r 92d431f1ec8d -r c5b74a88a3c0 jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java --- a/jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java Wed Sep 10 19:19:52 2014 +0400 +++ b/jdk/src/java.base/share/classes/java/lang/invoke/BoundMethodHandle.java Wed Sep 10 19:19:52 2014 +0400 @@ -54,6 +54,7 @@ /*non-public*/ BoundMethodHandle(MethodType type, LambdaForm form) { super(type, form); + assert(speciesData() == speciesData(form)); } // @@ -81,6 +82,11 @@ } } + /*non-public*/ + LambdaFormEditor editor() { + return form.editor(); + } + static BoundMethodHandle bindSingle(MethodType type, LambdaForm form, Object x) { return Species_L.make(type, form, x); } @@ -88,33 +94,23 @@ @Override // there is a default binder in the super class, for 'L' types only /*non-public*/ BoundMethodHandle bindArgumentL(int pos, Object value) { - MethodType type = type().dropParameterTypes(pos, pos+1); - LambdaForm form = internalForm().bind(1+pos, speciesData()); - return copyWithExtendL(type, form, value); + return editor().bindArgumentL(this, pos, value); } /*non-public*/ BoundMethodHandle bindArgumentI(int pos, int value) { - MethodType type = type().dropParameterTypes(pos, pos+1); - LambdaForm form = internalForm().bind(1+pos, speciesData()); - return copyWithExtendI(type, form, value); + return editor().bindArgumentI(this, pos, value); } /*non-public*/ BoundMethodHandle bindArgumentJ(int pos, long value) { - MethodType type = type().dropParameterTypes(pos, pos+1); - LambdaForm form = internalForm().bind(1+pos, speciesData()); - return copyWithExtendJ(type, form, value); + return editor().bindArgumentJ(this, pos, value); } /*non-public*/ BoundMethodHandle bindArgumentF(int pos, float value) { - MethodType type = type().dropParameterTypes(pos, pos+1); - LambdaForm form = internalForm().bind(1+pos, speciesData()); - return copyWithExtendF(type, form, value); + return editor().bindArgumentF(this, pos, value); } /*non-public*/ BoundMethodHandle bindArgumentD(int pos, double value) { - MethodType type = type().dropParameterTypes(pos, pos + 1); - LambdaForm form = internalForm().bind(1+pos, speciesData()); - return copyWithExtendD(type, form, value); + return editor().bindArgumentD(this, pos, value); } @Override @@ -149,6 +145,14 @@ */ /*non-public*/ abstract SpeciesData speciesData(); + /*non-public*/ static SpeciesData speciesData(LambdaForm form) { + Object c = form.names[0].constraint; + if (c instanceof SpeciesData) + return (SpeciesData) c; + // if there is no BMH constraint, then use the null constraint + return SpeciesData.EMPTY; + } + /** * Return the number of fields in this BMH. Equivalent to speciesData().fieldCount(). */ diff -r 92d431f1ec8d -r c5b74a88a3c0 jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java --- a/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java Wed Sep 10 19:19:52 2014 +0400 +++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaForm.java Wed Sep 10 19:19:52 2014 +0400 @@ -124,8 +124,7 @@ MemberName vmentry; // low-level behavior, or null if not yet prepared private boolean isCompiled; - // Caches for common structural transforms: - LambdaForm[] bindCache; + Object transformCache; // managed by LambdaFormEditor public static final int VOID_RESULT = -1, LAST_RESULT = -2; @@ -213,6 +212,13 @@ } return btypes; } + static byte[] basicTypesOrd(BasicType[] btypes) { + byte[] ords = new byte[btypes.length]; + for (int i = 0; i < btypes.length; i++) { + ords[i] = (byte)btypes[i].ordinal(); + } + return ords; + } static boolean isBasicTypeChar(char c) { return "LIJFDV".indexOf(c) >= 0; } @@ -408,7 +414,7 @@ * This allows Name references to be freely reused to construct * fresh lambdas, without confusion. */ - private boolean nameRefsAreLegal() { + boolean nameRefsAreLegal() { assert(arity >= 0 && arity <= names.length); assert(result >= -1 && result < names.length); // Do all names possess an index consistent with their local definition order? @@ -887,87 +893,8 @@ public int hashCode() { return result + 31 * Arrays.hashCode(names); } - - LambdaForm bind(int namePos, BoundMethodHandle.SpeciesData oldData) { - Name name = names[namePos]; - BoundMethodHandle.SpeciesData newData = oldData.extendWith(name.type); - return bind(name, new Name(newData.getterFunction(oldData.fieldCount()), names[0]), oldData, newData); - } - LambdaForm bind(Name name, Name binding, - BoundMethodHandle.SpeciesData oldData, - BoundMethodHandle.SpeciesData newData) { - int pos = name.index; - assert(name.isParam()); - assert(!binding.isParam()); - assert(name.type == binding.type); - assert(0 <= pos && pos < arity && names[pos] == name); - assert(binding.function.memberDeclaringClassOrNull() == newData.fieldHolder()); - assert(oldData.getterFunctions().length == newData.getterFunctions().length-1); - if (bindCache != null) { - LambdaForm form = bindCache[pos]; - if (form != null) { - assert(form.contains(binding)) : "form << " + form + " >> does not contain binding << " + binding + " >>"; - return form; - } - } else { - bindCache = new LambdaForm[arity]; - } - assert(nameRefsAreLegal()); - int arity2 = arity-1; - Name[] names2 = names.clone(); - names2[pos] = binding; // we might move this in a moment - - // The newly created LF will run with a different BMH. - // Switch over any pre-existing BMH field references to the new BMH class. - int firstOldRef = -1; - for (int i = 0; i < names2.length; i++) { - Name n = names[i]; - if (n.function != null && - n.function.memberDeclaringClassOrNull() == oldData.fieldHolder()) { - MethodHandle oldGetter = n.function.resolvedHandle; - MethodHandle newGetter = null; - for (int j = 0; j < oldData.getterHandles().length; j++) { - if (oldGetter == oldData.getterHandles()[j]) - newGetter = newData.getterHandles()[j]; - } - if (newGetter != null) { - if (firstOldRef < 0) firstOldRef = i; - Name n2 = new Name(newGetter, n.arguments); - names2[i] = n2; - } - } - } - - // Walk over the new list of names once, in forward order. - // Replace references to 'name' with 'binding'. - // Replace data structure references to the old BMH species with the new. - // This might cause a ripple effect, but it will settle in one pass. - assert(firstOldRef < 0 || firstOldRef > pos); - for (int i = pos+1; i < names2.length; i++) { - if (i <= arity2) continue; - names2[i] = names2[i].replaceNames(names, names2, pos, i); - } - - // (a0, a1, name=a2, a3, a4) => (a0, a1, a3, a4, binding) - int insPos = pos; - for (; insPos+1 < names2.length; insPos++) { - Name n = names2[insPos+1]; - if (n.isParam()) { - names2[insPos] = n; - } else { - break; - } - } - names2[insPos] = binding; - - // Since we moved some stuff, maybe update the result reference: - int result2 = result; - if (result2 == pos) - result2 = insPos; - else if (result2 > pos && result2 <= insPos) - result2 -= 1; - - return bindCache[pos] = new LambdaForm(debugName, arity2, names2, result2); + LambdaFormEditor editor() { + return LambdaFormEditor.lambdaFormEditor(this); } boolean contains(Name name) { diff -r 92d431f1ec8d -r c5b74a88a3c0 jdk/src/java.base/share/classes/java/lang/invoke/LambdaFormBuffer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaFormBuffer.java Wed Sep 10 19:19:52 2014 +0400 @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.invoke; + +import java.util.ArrayList; +import java.util.Arrays; +import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.LambdaForm.BasicType.*; + +/** Working storage for an LF that is being transformed. + * Similarly to a StringBuffer, the editing can take place in multiple steps. + */ +final class LambdaFormBuffer { + private int arity, length; + private Name[] names; + private Name[] originalNames; // snapshot of pre-transaction names + private byte flags; + private int firstChange; + private Name resultName; + private String debugName; + private ArrayList dups; + + private static final int F_TRANS = 0x10, F_OWNED = 0x03; + + LambdaFormBuffer(LambdaForm lf) { + this(lf.arity, lf.names, lf.result); + debugName = lf.debugName; + assert(lf.nameRefsAreLegal()); + } + + private LambdaFormBuffer(int arity, Name[] names, int result) { + this.arity = arity; + setNames(names); + if (result == LAST_RESULT) result = length - 1; + if (result >= 0 && names[result].type != V_TYPE) + resultName = names[result]; + } + + LambdaForm lambdaForm() { + assert(!inTrans()); // need endEdit call to tidy things up + return new LambdaForm(debugName, arity, nameArray(), resultIndex()); + } + + Name name(int i) { + assert(i < length); + return names[i]; + } + + Name[] nameArray() { + return Arrays.copyOf(names, length); + } + + int resultIndex() { + if (resultName == null) return VOID_RESULT; + int index = indexOf(resultName, names); + assert(index >= 0); + return index; + } + + void setNames(Name[] names2) { + names = originalNames = names2; // keep a record of where everything was to start with + length = names2.length; + flags = 0; + } + + private boolean verifyArity() { + for (int i = 0; i < arity && i < firstChange; i++) { + assert(names[i].isParam()) : "#" + i + "=" + names[i]; + } + for (int i = arity; i < length; i++) { + assert(!names[i].isParam()) : "#" + i + "=" + names[i]; + } + for (int i = length; i < names.length; i++) { + assert(names[i] == null) : "#" + i + "=" + names[i]; + } + // check resultName also + if (resultName != null) { + int resultIndex = indexOf(resultName, names); + assert(resultIndex >= 0) : "not found: " + resultName.exprString() + Arrays.asList(names); + assert(names[resultIndex] == resultName); + } + return true; + } + + private boolean verifyFirstChange() { + assert(inTrans()); + for (int i = 0; i < length; i++) { + if (names[i] != originalNames[i]) { + assert(firstChange == i) : Arrays.asList(firstChange, i, originalNames[i].exprString(), Arrays.asList(names)); + return true; + } + } + assert(firstChange == length) : Arrays.asList(firstChange, Arrays.asList(names)); + return true; + } + + private static int indexOf(NamedFunction fn, NamedFunction[] fns) { + for (int i = 0; i < fns.length; i++) { + if (fns[i] == fn) return i; + } + return -1; + } + + private static int indexOf(Name n, Name[] ns) { + for (int i = 0; i < ns.length; i++) { + if (ns[i] == n) return i; + } + return -1; + } + + boolean inTrans() { + return (flags & F_TRANS) != 0; + } + + int ownedCount() { + return flags & F_OWNED; + } + + void growNames(int insertPos, int growLength) { + int oldLength = length; + int newLength = oldLength + growLength; + int oc = ownedCount(); + if (oc == 0 || newLength > names.length) { + names = Arrays.copyOf(names, (names.length + growLength) * 5 / 4); + if (oc == 0) { + flags++; + oc++; + assert(ownedCount() == oc); + } + } + if (originalNames != null && originalNames.length < names.length) { + originalNames = Arrays.copyOf(originalNames, names.length); + if (oc == 1) { + flags++; + oc++; + assert(ownedCount() == oc); + } + } + if (growLength == 0) return; + int insertEnd = insertPos + growLength; + int tailLength = oldLength - insertPos; + System.arraycopy(names, insertPos, names, insertEnd, tailLength); + Arrays.fill(names, insertPos, insertEnd, null); + if (originalNames != null) { + System.arraycopy(originalNames, insertPos, originalNames, insertEnd, tailLength); + Arrays.fill(originalNames, insertPos, insertEnd, null); + } + length = newLength; + if (firstChange >= insertPos) { + firstChange += growLength; + } + } + + int lastIndexOf(Name n) { + int result = -1; + for (int i = 0; i < length; i++) { + if (names[i] == n) result = i; + } + return result; + } + + /** We have just overwritten the name at pos1 with the name at pos2. + * This means that there are two copies of the name, which we will have to fix later. + */ + private void noteDuplicate(int pos1, int pos2) { + Name n = names[pos1]; + assert(n == names[pos2]); + assert(originalNames[pos1] != null); // something was replaced at pos1 + assert(originalNames[pos2] == null || originalNames[pos2] == n); + if (dups == null) { + dups = new ArrayList<>(); + } + dups.add(n); + } + + /** Replace duplicate names by nulls, and remove all nulls. */ + private void clearDuplicatesAndNulls() { + if (dups != null) { + // Remove duplicates. + assert(ownedCount() >= 1); + for (Name dup : dups) { + for (int i = firstChange; i < length; i++) { + if (names[i] == dup && originalNames[i] != dup) { + names[i] = null; + assert(Arrays.asList(names).contains(dup)); + break; // kill only one dup + } + } + } + dups.clear(); + } + // Now that we are done with originalNames, remove "killed" names. + int oldLength = length; + for (int i = firstChange; i < length; i++) { + if (names[i] == null) { + System.arraycopy(names, i + 1, names, i, (--length - i)); + --i; // restart loop at this position + } + } + if (length < oldLength) { + Arrays.fill(names, length, oldLength, null); + } + assert(!Arrays.asList(names).subList(0, length).contains(null)); + } + + /** Create a private, writable copy of names. + * Preserve the original copy, for reference. + */ + void startEdit() { + assert(verifyArity()); + int oc = ownedCount(); + assert(!inTrans()); // no nested transactions + flags |= F_TRANS; + Name[] oldNames = names; + Name[] ownBuffer = (oc == 2 ? originalNames : null); + assert(ownBuffer != oldNames); + if (ownBuffer != null && ownBuffer.length >= length) { + names = copyNamesInto(ownBuffer); + } else { + // make a new buffer to hold the names + final int SLOP = 2; + names = Arrays.copyOf(oldNames, Math.max(length + SLOP, oldNames.length)); + if (oc < 2) ++flags; + assert(ownedCount() == oc + 1); + } + originalNames = oldNames; + assert(originalNames != names); + firstChange = length; + assert(inTrans()); + } + + private void changeName(int i, Name name) { + assert(inTrans()); + assert(i < length); + Name oldName = names[i]; + assert(oldName == originalNames[i]); // no multiple changes + assert(verifyFirstChange()); + if (ownedCount() == 0) + growNames(0, 0); + names[i] = name; + if (firstChange > i) { + firstChange = i; + } + if (resultName != null && resultName == oldName) { + resultName = name; + } + } + + /** Change the result name. Null means a void result. */ + void setResult(Name name) { + assert(name == null || lastIndexOf(name) >= 0); + resultName = name; + } + + /** Finish a transaction. */ + void endEdit() { + assert(verifyFirstChange()); + // Assuming names have been changed pairwise from originalNames[i] to names[i], + // update arguments to ensure referential integrity. + for (int i = Math.max(firstChange, arity); i < length; i++) { + Name name = names[i]; + if (name == null) continue; // space for removed duplicate + Name newName = name.replaceNames(originalNames, names, firstChange, i); + if (newName != name) { + names[i] = newName; + if (resultName == name) { + resultName = newName; + } + } + } + assert(inTrans()); + flags &= ~F_TRANS; + clearDuplicatesAndNulls(); + originalNames = null; + // If any parameters have been changed, then reorder them as needed. + // This is a "sheep-and-goats" stable sort, pushing all non-parameters + // to the right of all parameters. + if (firstChange < arity) { + Name[] exprs = new Name[arity - firstChange]; + int argp = firstChange, exprp = 0; + for (int i = firstChange; i < arity; i++) { + Name name = names[i]; + if (name.isParam()) { + names[argp++] = name; + } else { + exprs[exprp++] = name; + } + } + assert(exprp == (arity - argp)); + // copy the exprs just after the last remaining param + System.arraycopy(exprs, 0, names, argp, exprp); + // adjust arity + arity -= exprp; + } + assert(verifyArity()); + } + + private Name[] copyNamesInto(Name[] buffer) { + System.arraycopy(names, 0, buffer, 0, length); + Arrays.fill(buffer, length, buffer.length, null); + return buffer; + } + + /** Replace any Name whose function is in oldFns with a copy + * whose function is in the corresponding position in newFns. + * Only do this if the arguments are exactly equal to the given. + */ + LambdaFormBuffer replaceFunctions(NamedFunction[] oldFns, NamedFunction[] newFns, + Object... forArguments) { + assert(inTrans()); + if (oldFns.length == 0) return this; + for (int i = arity; i < length; i++) { + Name n = names[i]; + int nfi = indexOf(n.function, oldFns); + if (nfi >= 0 && Arrays.equals(n.arguments, forArguments)) { + changeName(i, new Name(newFns[nfi], n.arguments)); + } + } + return this; + } + + private void replaceName(int pos, Name binding) { + assert(inTrans()); + assert(verifyArity()); + assert(pos < arity); + Name param = names[pos]; + assert(param.isParam()); + assert(param.type == binding.type); + changeName(pos, binding); + } + + /** Replace a parameter by a fresh parameter. */ + LambdaFormBuffer renameParameter(int pos, Name newParam) { + assert(newParam.isParam()); + replaceName(pos, newParam); + return this; + } + + /** Replace a parameter by a fresh expression. */ + LambdaFormBuffer replaceParameterByNewExpression(int pos, Name binding) { + assert(!binding.isParam()); + assert(lastIndexOf(binding) < 0); // else use replaceParameterByCopy + replaceName(pos, binding); + return this; + } + + /** Replace a parameter by another parameter or expression already in the form. */ + LambdaFormBuffer replaceParameterByCopy(int pos, int valuePos) { + assert(pos != valuePos); + replaceName(pos, names[valuePos]); + noteDuplicate(pos, valuePos); // temporarily, will occur twice in the names array + return this; + } + + private void insertName(int pos, Name expr, boolean isParameter) { + assert(inTrans()); + assert(verifyArity()); + assert(isParameter ? pos <= arity : pos >= arity); + growNames(pos, 1); + if (isParameter) arity += 1; + changeName(pos, expr); + } + + /** Insert a fresh expression. */ + LambdaFormBuffer insertExpression(int pos, Name expr) { + assert(!expr.isParam()); + insertName(pos, expr, false); + return this; + } + + /** Insert a fresh parameter. */ + LambdaFormBuffer insertParameter(int pos, Name param) { + assert(param.isParam()); + insertName(pos, param, true); + return this; + } +} diff -r 92d431f1ec8d -r c5b74a88a3c0 jdk/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java Wed Sep 10 19:19:52 2014 +0400 @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.invoke; + +import java.util.Arrays; +import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.LambdaForm.BasicType.*; +import static java.lang.invoke.MethodHandleImpl.Intrinsic; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + +import sun.invoke.util.Wrapper; + +/** Transforms on LFs. + * A lambda-form editor can derive new LFs from its base LF. + * The editor can cache derived LFs, which simplifies the reuse of their underlying bytecodes. + * To support this caching, a LF has an optional pointer to its editor. + */ +class LambdaFormEditor { + final LambdaForm lambdaForm; + + private LambdaFormEditor(LambdaForm lambdaForm) { + this.lambdaForm = lambdaForm; + } + + // Factory method. + static LambdaFormEditor lambdaFormEditor(LambdaForm lambdaForm) { + // TO DO: Consider placing intern logic here, to cut down on duplication. + // lambdaForm = findPreexistingEquivalent(lambdaForm) + return new LambdaFormEditor(lambdaForm); + } + + /** A description of a cached transform, possibly associated with the result of the transform. + * The logical content is a sequence of byte values, starting with a Kind.ordinal value. + * The sequence is unterminated, ending with an indefinite number of zero bytes. + * Sequences that are simple (short enough and with small enough values) pack into a 64-bit long. + */ + private static final class Transform { + final long packedBytes; + final byte[] fullBytes; + final LambdaForm result; // result of transform, or null, if there is none available + + private enum Kind { + NO_KIND, // necessary because ordinal must be greater than zero + BIND_ARG, ADD_ARG, DUP_ARG, + SPREAD_ARGS, + FILTER_ARG, FILTER_RETURN, FILTER_RETURN_TO_ZERO, + COLLECT_ARGS, COLLECT_ARGS_TO_VOID, COLLECT_ARGS_TO_ARRAY, + FOLD_ARGS, FOLD_ARGS_TO_VOID, + PERMUTE_ARGS + //maybe add more for guard with test, catch exception, pointwise type conversions + } + + private static final boolean STRESS_TEST = false; // turn on to disable most packing + private static final int + PACKED_BYTE_SIZE = (STRESS_TEST ? 2 : 4), + PACKED_BYTE_MASK = (1 << PACKED_BYTE_SIZE) - 1, + PACKED_BYTE_MAX_LENGTH = (STRESS_TEST ? 3 : 64 / PACKED_BYTE_SIZE); + + private static long packedBytes(byte[] bytes) { + if (bytes.length > PACKED_BYTE_MAX_LENGTH) return 0; + long pb = 0; + int bitset = 0; + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i] & 0xFF; + bitset |= b; + pb |= (long)b << (i * PACKED_BYTE_SIZE); + } + if (!inRange(bitset)) + return 0; + return pb; + } + private static long packedBytes(int b0, int b1) { + assert(inRange(b0 | b1)); + return ( (b0 << 0*PACKED_BYTE_SIZE) + | (b1 << 1*PACKED_BYTE_SIZE)); + } + private static long packedBytes(int b0, int b1, int b2) { + assert(inRange(b0 | b1 | b2)); + return ( (b0 << 0*PACKED_BYTE_SIZE) + | (b1 << 1*PACKED_BYTE_SIZE) + | (b2 << 2*PACKED_BYTE_SIZE)); + } + private static long packedBytes(int b0, int b1, int b2, int b3) { + assert(inRange(b0 | b1 | b2 | b3)); + return ( (b0 << 0*PACKED_BYTE_SIZE) + | (b1 << 1*PACKED_BYTE_SIZE) + | (b2 << 2*PACKED_BYTE_SIZE) + | (b3 << 3*PACKED_BYTE_SIZE)); + } + private static boolean inRange(int bitset) { + assert((bitset & 0xFF) == bitset); // incoming values must fit in *unsigned* byte + return ((bitset & ~PACKED_BYTE_MASK) == 0); + } + private static byte[] fullBytes(int... byteValues) { + byte[] bytes = new byte[byteValues.length]; + int i = 0; + for (int bv : byteValues) { + bytes[i++] = bval(bv); + } + assert(packedBytes(bytes) == 0); + return bytes; + } + + private byte byteAt(int i) { + long pb = packedBytes; + if (pb == 0) { + if (i >= fullBytes.length) return 0; + return fullBytes[i]; + } + assert(fullBytes == null); + if (i > PACKED_BYTE_MAX_LENGTH) return 0; + int pos = (i * PACKED_BYTE_SIZE); + return (byte)((pb >>> pos) & PACKED_BYTE_MASK); + } + + Kind kind() { return Kind.values()[byteAt(0)]; } + + private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) { + this.packedBytes = packedBytes; + this.fullBytes = fullBytes; + this.result = result; + } + private Transform(long packedBytes) { + this(packedBytes, null, null); + assert(packedBytes != 0); + } + private Transform(byte[] fullBytes) { + this(0, fullBytes, null); + } + + private static byte bval(int b) { + assert((b & 0xFF) == b); // incoming value must fit in *unsigned* byte + return (byte)b; + } + private static byte bval(Kind k) { + return bval(k.ordinal()); + } + static Transform of(Kind k, int b1) { + byte b0 = bval(k); + if (inRange(b0 | b1)) + return new Transform(packedBytes(b0, b1)); + else + return new Transform(fullBytes(b0, b1)); + } + static Transform of(Kind k, int b1, int b2) { + byte b0 = (byte) k.ordinal(); + if (inRange(b0 | b1 | b2)) + return new Transform(packedBytes(b0, b1, b2)); + else + return new Transform(fullBytes(b0, b1, b2)); + } + static Transform of(Kind k, int b1, int b2, int b3) { + byte b0 = (byte) k.ordinal(); + if (inRange(b0 | b1 | b2 | b3)) + return new Transform(packedBytes(b0, b1, b2, b3)); + else + return new Transform(fullBytes(b0, b1, b2, b3)); + } + private static final byte[] NO_BYTES = {}; + static Transform of(Kind k, int... b123) { + return ofBothArrays(k, b123, NO_BYTES); + } + static Transform of(Kind k, int b1, byte[] b234) { + return ofBothArrays(k, new int[]{ b1 }, b234); + } + static Transform of(Kind k, int b1, int b2, byte[] b345) { + return ofBothArrays(k, new int[]{ b1, b2 }, b345); + } + private static Transform ofBothArrays(Kind k, int[] b123, byte[] b456) { + byte[] fullBytes = new byte[1 + b123.length + b456.length]; + int i = 0; + fullBytes[i++] = bval(k); + for (int bv : b123) { + fullBytes[i++] = bval(bv); + } + for (byte bv : b456) { + fullBytes[i++] = bv; + } + long packedBytes = packedBytes(fullBytes); + if (packedBytes != 0) + return new Transform(packedBytes); + else + return new Transform(fullBytes); + } + + Transform withResult(LambdaForm result) { + return new Transform(this.packedBytes, this.fullBytes, result); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Transform && equals((Transform)obj); + } + public boolean equals(Transform that) { + return this.packedBytes == that.packedBytes && Arrays.equals(this.fullBytes, that.fullBytes); + } + @Override + public int hashCode() { + if (packedBytes != 0) { + assert(fullBytes == null); + return Long.hashCode(packedBytes); + } + return Arrays.hashCode(fullBytes); + } + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + long bits = packedBytes; + if (bits != 0) { + buf.append("("); + while (bits != 0) { + buf.append(bits & PACKED_BYTE_MASK); + bits >>>= PACKED_BYTE_SIZE; + if (bits != 0) buf.append(","); + } + buf.append(")"); + } + if (fullBytes != null) { + buf.append("unpacked"); + buf.append(Arrays.toString(fullBytes)); + } + if (result != null) { + buf.append(" result="); + buf.append(result); + } + return buf.toString(); + } + } + + /** Find a previously cached transform equivalent to the given one, and return its result. */ + private LambdaForm getInCache(Transform key) { + assert(key.result == null); + // The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap. + Object c = lambdaForm.transformCache; + Transform k = null; + if (c instanceof ConcurrentHashMap) { + @SuppressWarnings("unchecked") + ConcurrentHashMap m = (ConcurrentHashMap) c; + k = m.get(key); + } else if (c == null) { + return null; + } else if (c instanceof Transform) { + // one-element cache avoids overhead of an array + Transform t = (Transform)c; + if (t.equals(key)) k = t; + } else { + Transform[] ta = (Transform[])c; + for (int i = 0; i < ta.length; i++) { + Transform t = ta[i]; + if (t == null) break; + if (t.equals(key)) { k = t; break; } + } + } + assert(k == null || key.equals(k)); + return k == null ? null : k.result; + } + + /** Arbitrary but reasonable limits on Transform[] size for cache. */ + private static final int MIN_CACHE_ARRAY_SIZE = 4, MAX_CACHE_ARRAY_SIZE = 16; + + /** Cache a transform with its result, and return that result. + * But if an equivalent transform has already been cached, return its result instead. + */ + private LambdaForm putInCache(Transform key, LambdaForm form) { + key = key.withResult(form); + for (int pass = 0; ; pass++) { + Object c = lambdaForm.transformCache; + if (c instanceof ConcurrentHashMap) { + @SuppressWarnings("unchecked") + ConcurrentHashMap m = (ConcurrentHashMap) c; + Transform k = m.putIfAbsent(key, key); + return k != null ? k.result : form; + } + assert(pass == 0); + synchronized (lambdaForm) { + c = lambdaForm.transformCache; + if (c instanceof ConcurrentHashMap) + continue; + if (c == null) { + lambdaForm.transformCache = key; + return form; + } + Transform[] ta; + if (c instanceof Transform) { + Transform k = (Transform)c; + if (k.equals(key)) { + return k.result; + } + // expand one-element cache to small array + ta = new Transform[MIN_CACHE_ARRAY_SIZE]; + ta[0] = k; + lambdaForm.transformCache = c = ta; + } else { + // it is already expanded + ta = (Transform[])c; + } + int len = ta.length; + int i; + for (i = 0; i < len; i++) { + Transform k = ta[i]; + if (k == null) { + break; + } + if (k.equals(key)) { + return k.result; + } + } + if (i < len) { + // just fall through to cache update + } else if (len < MAX_CACHE_ARRAY_SIZE) { + len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE); + ta = Arrays.copyOf(ta, len); + lambdaForm.transformCache = ta; + } else { + ConcurrentHashMap m = new ConcurrentHashMap<>(MAX_CACHE_ARRAY_SIZE * 2); + for (Transform k : ta) { + m.put(k, k); + } + lambdaForm.transformCache = m; + // The second iteration will update for this query, concurrently. + continue; + } + ta[i] = key; + return form; + } + } + } + + private LambdaFormBuffer buffer() { + return new LambdaFormBuffer(lambdaForm); + } + + /// Editing methods for method handles. These need to have fast paths. + + private BoundMethodHandle.SpeciesData oldSpeciesData() { + return BoundMethodHandle.speciesData(lambdaForm); + } + private BoundMethodHandle.SpeciesData newSpeciesData(BasicType type) { + return oldSpeciesData().extendWith(type); + } + + BoundMethodHandle bindArgumentL(BoundMethodHandle mh, int pos, Object value) { + assert(mh.speciesData() == oldSpeciesData()); + BasicType bt = L_TYPE; + MethodType type2 = bindArgumentType(mh, pos, bt); + LambdaForm form2 = bindArgumentForm(1+pos); + return mh.copyWithExtendL(type2, form2, value); + } + BoundMethodHandle bindArgumentI(BoundMethodHandle mh, int pos, int value) { + assert(mh.speciesData() == oldSpeciesData()); + BasicType bt = I_TYPE; + MethodType type2 = bindArgumentType(mh, pos, bt); + LambdaForm form2 = bindArgumentForm(1+pos); + return mh.copyWithExtendI(type2, form2, value); + } + + BoundMethodHandle bindArgumentJ(BoundMethodHandle mh, int pos, long value) { + assert(mh.speciesData() == oldSpeciesData()); + BasicType bt = J_TYPE; + MethodType type2 = bindArgumentType(mh, pos, bt); + LambdaForm form2 = bindArgumentForm(1+pos); + return mh.copyWithExtendJ(type2, form2, value); + } + + BoundMethodHandle bindArgumentF(BoundMethodHandle mh, int pos, float value) { + assert(mh.speciesData() == oldSpeciesData()); + BasicType bt = F_TYPE; + MethodType type2 = bindArgumentType(mh, pos, bt); + LambdaForm form2 = bindArgumentForm(1+pos); + return mh.copyWithExtendF(type2, form2, value); + } + + BoundMethodHandle bindArgumentD(BoundMethodHandle mh, int pos, double value) { + assert(mh.speciesData() == oldSpeciesData()); + BasicType bt = D_TYPE; + MethodType type2 = bindArgumentType(mh, pos, bt); + LambdaForm form2 = bindArgumentForm(1+pos); + return mh.copyWithExtendD(type2, form2, value); + } + + private MethodType bindArgumentType(BoundMethodHandle mh, int pos, BasicType bt) { + assert(mh.form == lambdaForm); + assert(mh.form.names[1+pos].type == bt); + assert(BasicType.basicType(mh.type().parameterType(pos)) == bt); + return mh.type().dropParameterTypes(pos, pos+1); + } + + /// Editing methods for lambda forms. + // Each editing method can (potentially) cache the edited LF so that it can be reused later. + + LambdaForm bindArgumentForm(int pos) { + Transform key = Transform.of(Transform.Kind.BIND_ARG, pos); + LambdaForm form = getInCache(key); + if (form != null) { + assert(form.parameterConstraint(0) == newSpeciesData(lambdaForm.parameterType(pos))); + return form; + } + LambdaFormBuffer buf = buffer(); + buf.startEdit(); + + BoundMethodHandle.SpeciesData oldData = oldSpeciesData(); + BoundMethodHandle.SpeciesData newData = newSpeciesData(lambdaForm.parameterType(pos)); + Name oldBaseAddress = lambdaForm.parameter(0); // BMH holding the values + Name newBaseAddress; + NamedFunction getter = newData.getterFunction(oldData.fieldCount()); + + if (pos != 0) { + // The newly created LF will run with a different BMH. + // Switch over any pre-existing BMH field references to the new BMH class. + buf.replaceFunctions(oldData.getterFunctions(), newData.getterFunctions(), oldBaseAddress); + newBaseAddress = oldBaseAddress.withConstraint(newData); + buf.renameParameter(0, newBaseAddress); + buf.replaceParameterByNewExpression(pos, new Name(getter, newBaseAddress)); + } else { + // cannot bind the MH arg itself, unless oldData is empty + assert(oldData == BoundMethodHandle.SpeciesData.EMPTY); + newBaseAddress = new Name(L_TYPE).withConstraint(newData); + buf.replaceParameterByNewExpression(0, new Name(getter, newBaseAddress)); + buf.insertParameter(0, newBaseAddress); + } + + buf.endEdit(); + form = buf.lambdaForm(); + return putInCache(key, form); + } +} diff -r 92d431f1ec8d -r c5b74a88a3c0 jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 10 19:19:52 2014 +0400 +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java Wed Sep 10 19:19:52 2014 +0400 @@ -530,7 +530,7 @@ * Pre-initialized NamedFunctions for bootstrapping purposes. * Factored in an inner class to delay initialization until first usage. */ - private static class Lazy { + static class Lazy { private static final Class MHI = MethodHandleImpl.class; static final NamedFunction NF_checkSpreadArgument;