/* * Copyright (c) 2016, 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 jdk.experimental.value; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import jdk.experimental.value.ValueType.ValueHandleKind.ValueHandleKey; import jdk.experimental.bytecode.MacroCodeBuilder.CondKind; import jdk.experimental.bytecode.TypeTag; import jdk.internal.misc.Unsafe; import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.Wrapper; import valhalla.shady.MinimalValueTypes_1_0; // Rough place holder just now... public class ValueType { static final Unsafe UNSAFE = Unsafe.getUnsafe(); enum ValueHandleKind { BOX, UNBOX, DEFAULT, EQ, HASH, WITHER() { @Override ValueHandleKey key(Object fieldName) { return new ValueHandleKey(this, fieldName); } }, NEWARRAY, VALOAD, VASTORE, ARRAYLENGTH, MULTINEWARRAY() { @Override ValueHandleKey key(Object dims) { return new ValueHandleKey(this, dims); } }, IDENTITY, GETTER() { @Override ValueHandleKey key(Object fieldName) { return new ValueHandleKey(this, fieldName); } }; ValueHandleKey key() { return new ValueHandleKey(this, null); } ValueHandleKey key(Object optArg) { throw new IllegalStateException(); } static class ValueHandleKey { ValueHandleKind kind; Optional optArg; ValueHandleKey(ValueHandleKind kind, Object optArg) { this.kind = kind; this.optArg = Optional.ofNullable(optArg); } @Override public boolean equals(Object obj) { if (obj instanceof ValueHandleKey) { ValueHandleKey that = (ValueHandleKey)obj; return Objects.equals(kind, that.kind) && Objects.equals(optArg, that.optArg); } else { return false; } } @Override public int hashCode() { return Objects.hashCode(kind) * 31 + Objects.hashCode(optArg); } } } private static final Lookup IMPL_LOOKUP; static { try { Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP"); f.setAccessible(true); IMPL_LOOKUP = (Lookup)f.get(null); } catch (ReflectiveOperationException ex) { throw new AssertionError(ex); } } private static final ConcurrentHashMap, ValueType> BOX_TO_VT = new ConcurrentHashMap<>(); public static boolean classHasValueType(Class x) { if (!MinimalValueTypes_1_0.isValueCapable(x)) { return false; } return MinimalValueTypes_1_0.getValueTypeClass(x) != null; } @SuppressWarnings("unchecked") public static ValueType forClass(Class x) { if (!MinimalValueTypes_1_0.isValueCapable(x)) { throw new IllegalArgumentException("Class " + x + " not a value capable class"); } ValueType vt = (ValueType) BOX_TO_VT.get(x); if (vt != null) { return vt; } Class valueClass = (Class) MinimalValueTypes_1_0.getValueTypeClass(x); vt = new ValueType(x, valueClass); ValueType old = (ValueType) BOX_TO_VT.putIfAbsent(x, vt); if (old != null) { vt = old; } return vt; } private Lookup boxLookup; private Lookup valueLookup; private Map handleMap = new ConcurrentHashMap<>(); private ValueType(Class boxClass, Class valueClass) { this.boxLookup = IMPL_LOOKUP.in(boxClass); this.valueLookup = IMPL_LOOKUP.in(valueClass); } @SuppressWarnings("unchecked") public Class boxClass() { return (Class)boxLookup.lookupClass(); } public Class sourceClass() { return boxClass(); } public Class valueClass() { return valueLookup.lookupClass(); } public Class arrayValueClass() { return arrayValueClass(1); } public Class arrayValueClass(int dims) { String dimsStr = "[[[[[[[[[[[[[[[["; if (dims < 1 || dims > 16) { throw new IllegalArgumentException("cannot create array class for dimension > 16"); } String cn = dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";"; return MinimalValueTypes_1_0.loadValueTypeClass(boxLookup.lookupClass(), cn); } public String toString() { return "ValueType boxClass=" + boxClass() + " valueClass=" + valueClass(); } String mhName(String opName) { return sourceClass().getName() + "_" + opName; } public MethodHandle defaultValueConstant() { ValueHandleKey key = ValueHandleKind.DEFAULT.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("default"), MethodType.methodType(valueClass()), C -> { C.vdefault(valueClass()).vreturn(); }); handleMap.put(key, result); } return result; } public MethodHandle substitutabilityTest() { ValueHandleKey key = ValueHandleKind.EQ.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(valueLookup, mhName("subTest"), MethodType.methodType(boolean.class, valueClass(), valueClass()), C -> { for (Field f : valueFields()) { String fDesc = BytecodeDescriptor.unparse(f.getType()); C.vload(0).vgetfield(valueClass(), f.getName(), fDesc); C.vload(1).vgetfield(valueClass(), f.getName(), fDesc); if (f.getType().isPrimitive()) { C.ifcmp(fDesc, CondKind.NE, "fail"); } else { C.invokestatic(Objects.class, "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false); C.const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail"); } } C.const_(1); C.ireturn(); C.label("fail"); C.const_(0); C.ireturn(); }); handleMap.put(key, result); } return result; } public MethodHandle substitutabilityHashCode() { ValueHandleKey key = ValueHandleKind.HASH.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(valueLookup, mhName("subHash"), MethodType.methodType(int.class, valueClass()), C -> { C.withLocal("res", "I"); C.const_(1).store("res"); for (Field f : valueFields()) { String desc = BytecodeDescriptor.unparse(f.getType()); C.vload(0).vgetfield(valueClass(), f.getName(), desc); if (f.getType().isPrimitive()) { C.invokestatic(Wrapper.asWrapperType(f.getType()), "hashCode", "(" + desc + ")I", false); } else { C.invokestatic(Objects.class, "hashCode", "(Ljava/lang/Object;)I", false); } C.load("res").const_(31).imul(); C.iadd().store("res"); } C.load("res").ireturn(); }); handleMap.put(key, result); } return result; } public MethodHandle findWither(Lookup lookup, String name, Class type) throws NoSuchFieldException, IllegalAccessException { ValueHandleKey key = ValueHandleKind.WITHER.key(List.of(name, type)); MethodHandle result = handleMap.get(key); if (result == null) { String fieldType = BytecodeDescriptor.unparse(type); result = MethodHandleBuilder.loadCode(valueLookup, mhName("wither$" + name), MethodType.methodType(valueClass(), valueClass(), type), C -> C.vload(0).load(1).vwithfield(valueClass(), name, fieldType).vreturn()); handleMap.put(key, result); } // Allow access if the lookup class is the VCC or DVT and the lookup // has private access Class lc = lookup.lookupClass(); if (lookup.hasPrivateAccess() && (valueClass() == lc || boxClass() == lc)) { return result; } throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to field %s.%s", lc.getName(), boxClass().getName(), name)); } public MethodHandle unbox() { ValueHandleKey key = ValueHandleKind.UNBOX.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("unbox"), MethodType.methodType(valueClass(), boxClass()), C -> { C.load(0).vunbox(valueClass()).vreturn(); }); handleMap.put(key, result); } return result; } public MethodHandle box() { ValueHandleKey key = ValueHandleKind.BOX.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("box"), MethodType.methodType(boxClass(), valueClass()), C -> { C.vload(0).vbox(boxClass()).areturn(); }); handleMap.put(key, result); } return result; } public MethodHandle newArray() { Class arrayValueClass = arrayValueClass(); ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("newArray"), MethodType.methodType(arrayValueClass, int.class), C -> { C.load(0).anewarray(valueClass()).areturn(); }); handleMap.put(key, result); } return result; } public MethodHandle arrayGetter() { Class arrayValueClass = arrayValueClass(); ValueHandleKey key = ValueHandleKind.VALOAD.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayGet"), MethodType.methodType(valueClass(), arrayValueClass, int.class), C -> { C.load(0).load(1).vaload().vreturn(); }); handleMap.put(key, result); } return result; } public MethodHandle arrayLength() { Class arrayValueClass = arrayValueClass(); ValueHandleKey key = ValueHandleKind.ARRAYLENGTH.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayLength"), MethodType.methodType(int.class, arrayValueClass), C -> { C.load(0).arraylength().ireturn(); }); handleMap.put(key, result); } return result; } public MethodHandle arraySetter() { Class arrayValueClass = arrayValueClass(); ValueHandleKey key = ValueHandleKind.VASTORE.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("arraySet"), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()), C -> { C.load(0).load(1).load(2).vastore().return_(); }); handleMap.put(key, result); } return result; } public MethodHandle newMultiArray(int dims) { Class arrayValueClass = arrayValueClass(dims); ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); MethodHandle result = handleMap.get(key); Class[] params = new Class[dims]; Arrays.fill(params, int.class); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("newMultiArray"), MethodType.methodType(arrayValueClass, params), C -> { for (int i = 0 ; i < dims ; i++) { C.load(i); } C.multianewarray(arrayValueClass, (byte)dims).areturn(); }); handleMap.put(key, result); } return result; } public MethodHandle identity() { ValueHandleKey key = ValueHandleKind.IDENTITY.key(); MethodHandle result = handleMap.get(key); if (result == null) { result = MethodHandleBuilder.loadCode(boxLookup, mhName("identity"), MethodType.methodType(valueClass(), valueClass()), C -> C.vload(0).vreturn()); handleMap.put(key, result); } return result; } public MethodHandle findGetter(Lookup lookup, String name, Class type) throws NoSuchFieldException, IllegalAccessException { ValueHandleKey key = ValueHandleKind.GETTER.key(List.of(name, type)); MethodHandle result = handleMap.get(key); if (result == null) { String fieldType = BytecodeDescriptor.unparse(type); result = MethodHandleBuilder.loadCode(boxLookup, mhName("getter$" + name), MethodType.methodType(type, valueClass()), C -> C.vload(0).vgetfield(valueClass(), name, fieldType).return_(fieldType)); handleMap.put(key, result); } //force access-check lookup.findGetter(boxClass(), name, type); return result; } private Field[] valueFields() { int valFieldMask = Modifier.FINAL; return Stream.of(sourceClass().getDeclaredFields()) .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask) .toArray(Field[]::new); } }