1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   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 jdk.experimental.value;
  27 
  28 import java.lang.invoke.MethodHandle;
  29 import java.lang.invoke.MethodHandles.Lookup;
  30 import java.lang.invoke.MethodType;
  31 import java.lang.reflect.Field;
  32 import java.lang.reflect.Method;
  33 import java.lang.reflect.Modifier;
  34 import java.util.Arrays;
  35 import java.util.Map;
  36 import java.util.Objects;
  37 import java.util.Optional;
  38 import java.util.concurrent.ConcurrentHashMap;
  39 import java.util.stream.Stream;
  40 
  41 import jdk.experimental.value.ValueType.ValueHandleKind.ValueHandleKey;
  42 import jdk.experimental.bytecode.MacroCodeBuilder.CondKind;
  43 import jdk.experimental.bytecode.TypeTag;
  44 import jdk.internal.misc.Unsafe;
  45 import sun.invoke.util.BytecodeDescriptor;
  46 import sun.invoke.util.Wrapper;
  47 import valhalla.shady.MinimalValueTypes_1_0;
  48 
  49 // Rough place holder just now...
  50 public class ValueType<T> {
  51 
  52     static final Unsafe UNSAFE = Unsafe.getUnsafe();
  53 
  54     enum ValueHandleKind {
  55         BOX,
  56         UNBOX,
  57         DEFAULT,
  58         EQ,
  59         HASH,
  60         WITHER() {
  61             @Override
  62             ValueHandleKey key(Object fieldName) {
  63                return new ValueHandleKey(this, fieldName);
  64             }
  65         },
  66         MAKE,
  67         NEWARRAY,
  68         VALOAD,
  69         VASTORE,
  70         MULTINEWARRAY() {
  71             @Override
  72             ValueHandleKey key(Object dims) {
  73                return new ValueHandleKey(this, dims);
  74             }
  75         };
  76 
  77         ValueHandleKey key() {
  78             return new ValueHandleKey(this, null);
  79         }
  80 
  81         ValueHandleKey key(Object optArg) {
  82             throw new IllegalStateException();
  83         }
  84 
  85         static class ValueHandleKey {
  86             ValueHandleKind kind;
  87             Optional<Object> optArg;
  88 
  89             ValueHandleKey(ValueHandleKind kind, Object optArg) {
  90                 this.kind = kind;
  91                 this.optArg = Optional.ofNullable(optArg);
  92             }
  93 
  94             @Override
  95             public boolean equals(Object obj) {
  96                 if (obj instanceof ValueHandleKey) {
  97                     ValueHandleKey that = (ValueHandleKey)obj;
  98                     return Objects.equals(kind, that.kind) &&
  99                             Objects.equals(optArg, that.optArg);
 100                 } else {
 101                     return false;
 102                 }
 103             }
 104 
 105             @Override
 106             public int hashCode() {
 107                 return Objects.hashCode(kind) * 31 + Objects.hashCode(optArg);
 108             }
 109         }
 110     }
 111 
 112     private static final Lookup IMPL_LOOKUP;
 113 
 114     static {
 115         try {
 116             Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP");
 117             f.setAccessible(true);
 118             IMPL_LOOKUP = (Lookup)f.get(null);
 119         } catch (ReflectiveOperationException ex) {
 120             throw new AssertionError(ex);
 121         }
 122     }
 123 
 124     private static final ConcurrentHashMap<Class<?>, ValueType<?>> BOX_TO_VT = new ConcurrentHashMap<>();
 125 
 126     public static boolean classHasValueType(Class<?> x) {
 127         return MinimalValueTypes_1_0.classHasValueType(x);
 128     }
 129 
 130     @SuppressWarnings("unchecked")
 131     public static <T> ValueType<T> forClass(Class<T> x) {
 132         ValueType<T> vt = (ValueType<T>) BOX_TO_VT.get(x);
 133         if (vt != null) {
 134             return vt;
 135         }
 136 
 137         try {
 138             Class<T> valueClass = (Class<T>) MinimalValueTypes_1_0.getValueTypeClass(x);
 139             vt = new ValueType<T>(x, valueClass);
 140             ValueType<T> old = (ValueType<T>) BOX_TO_VT.putIfAbsent(x, vt);
 141             if (old != null) {
 142                 vt = old;
 143             }
 144             return vt;
 145         }
 146         catch (ClassNotFoundException cne) {
 147             throw new IllegalArgumentException("Class " + x + " not bound to ValueType", cne);
 148         }
 149     }
 150 
 151     private Class<T> boxClass;
 152     private Class<?> valueClass;
 153     private Lookup lookup;
 154     private Map<ValueHandleKind.ValueHandleKey, MethodHandle> handleMap = new ConcurrentHashMap<>();
 155 
 156     private ValueType(Class<T> boxClass, Class<T> valueClass) {
 157         this.boxClass = boxClass;
 158         this.valueClass = valueClass;
 159         this.lookup = IMPL_LOOKUP.in(boxClass);
 160     }
 161 
 162     public Class<T> boxClass() {
 163         return boxClass;
 164     }
 165 
 166     public Class<?> sourceClass() {
 167         return boxClass();
 168     }
 169 
 170     public Class<?> valueClass() {
 171         return valueClass;
 172     }
 173 
 174     public Class<?> arrayValueClass() {
 175         return arrayValueClass(1);
 176     }
 177 
 178     public Class<?> arrayValueClass(int dims) {
 179         try {
 180             String dimsStr = "[[[[[[[[[[[[[[[[";
 181             if (dims < 1 || dims > 16) {
 182                 throw new IllegalArgumentException("cannot create array class for dimension > 16");
 183             }
 184             return Class.forName(dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";", false, lookup.lookupClass().getClassLoader());
 185         } catch (ClassNotFoundException ex) {
 186             throw new IllegalStateException(ex);
 187         }
 188     }
 189 
 190     public String toString() {
 191         return "ValueType boxClass=" + boxClass() + " valueClass=" + valueClass();
 192     }
 193 
 194     public MethodHandle defaultValueConstant() {
 195         ValueHandleKey key = ValueHandleKind.DEFAULT.key();
 196         MethodHandle result = handleMap.get(key);
 197         if (result == null) {
 198             result = MethodHandleBuilder.loadCode(lookup, "default" + sourceClass().getName(), MethodType.methodType(valueClass()),
 199                     C -> {
 200                         C.new_(boxClass()).vunbox(valueClass()).vreturn();
 201                     });
 202             handleMap.put(key, result);
 203         }
 204         return result;
 205     }
 206 
 207     public MethodHandle substitutabilityTest() {
 208         ValueHandleKey key = ValueHandleKind.EQ.key();
 209         MethodHandle result = handleMap.get(key);
 210         if (result == null) {
 211             result = MethodHandleBuilder.loadCode(lookup, "subTest" + sourceClass().getName(), MethodType.methodType(boolean.class, valueClass(), valueClass()),
 212                     C -> {
 213                         for (Field f : valueFields()) {
 214                             C.vload(0).vgetfield(valueClass(), f.getName(), BytecodeDescriptor.unparse(f.getType()));
 215                             C.vload(1).vgetfield(valueClass(), f.getName(), BytecodeDescriptor.unparse(f.getType()));
 216                             if (f.getType().isPrimitive()) {
 217                                 C.ifcmp(TypeTag.I, CondKind.NE, "fail");
 218                             } else {
 219                                 C.invokestatic(Objects.class, "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false);
 220                                 C.const_(0).ifcmp(TypeTag.I, CondKind.NE, "fail");
 221                             }
 222                         }
 223                         C.const_(1);
 224                         C.ireturn();
 225                         C.label("fail");
 226                         C.const_(0);
 227                         C.ireturn();
 228                     });
 229             handleMap.put(key, result);
 230         }
 231         return result;
 232     }
 233 
 234     public MethodHandle substitutabilityHashCode() {
 235         ValueHandleKey key = ValueHandleKind.HASH.key();
 236         MethodHandle result = handleMap.get(key);
 237         if (result == null) {
 238             result = MethodHandleBuilder.loadCode(lookup, "subHash" + sourceClass().getName(), MethodType.methodType(int.class, valueClass()),
 239                     C -> {
 240                         C.withLocal("res", "I");
 241                         C.const_(1).store("res");
 242                         for (Field f : valueFields()) {
 243                             String desc = BytecodeDescriptor.unparse(f.getType());
 244                             C.vload(0).vgetfield(valueClass(), f.getName(), desc);
 245                             C.load("res").const_(31).imul();
 246                             if (f.getType().isPrimitive()) {
 247                                 C.invokestatic(Wrapper.asWrapperType(f.getType()), "hashCode", "(" + desc + ")I", false);
 248                             } else {
 249                                 C.invokestatic(Objects.class, "hashCode", "(Ljava/lang/Object)I", false);
 250                             }
 251                             C.iadd().store("res");
 252                         }
 253                         C.load("res").ireturn();
 254                     });
 255             handleMap.put(key, result);
 256         }
 257         return result;
 258     }
 259 
 260     //Todo: when 'vwithfield' is ready, this handle could be greatly simplified
 261     public MethodHandle findWither(String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
 262         ValueHandleKey key = ValueHandleKind.WITHER.key(name);
 263         MethodHandle result = handleMap.get(key);
 264         if (result == null) {
 265             Field field = boxClass().getDeclaredField(name);
 266             if (field == null || !field.getType().equals(type) ||
 267                     (field.getModifiers() & Modifier.STATIC) != 0) {
 268                 throw new NoSuchFieldException(name);
 269             }
 270             Class<?> erasedType = type.isPrimitive() ?
 271                     type : Object.class;
 272             Method unsafeMethod = Stream.of(UNSAFE.getClass().getDeclaredMethods())
 273                     .filter(m -> m.getName().startsWith("put") &&
 274                             Arrays.asList(m.getParameterTypes()).equals(Arrays.asList(Object.class, long.class, erasedType)))
 275                     .findFirst().get();
 276             long fieldOffset = UNSAFE.objectFieldOffset(field);
 277             result = MethodHandleBuilder.loadCode(lookup, "wither" + sourceClass().getName() + ":" + name, MethodType.methodType(valueClass(), UNSAFE.getClass(), valueClass(), type),
 278                     C -> {
 279                         C.withLocal("boxedVal", BytecodeDescriptor.unparse(boxClass()))
 280                          .load(1)
 281                          .vbox(boxClass())
 282                          .store("boxedVal")
 283                          .load(0)
 284                          .load("boxedVal")
 285                          .const_(fieldOffset)
 286                          .load(2);
 287                          MethodType unsafeMT = MethodType.methodType(unsafeMethod.getReturnType(), unsafeMethod.getParameterTypes());
 288                          C.invokevirtual(UNSAFE.getClass(), unsafeMethod.getName(), BytecodeDescriptor.unparse(unsafeMT), false)
 289                           .load("boxedVal")
 290                           .vunbox(valueClass())
 291                           .vreturn();
 292                     }).bindTo(UNSAFE);
 293             handleMap.put(key, result);
 294         }
 295         return result;
 296     }
 297 
 298     public MethodHandle unbox() {
 299         ValueHandleKey key = ValueHandleKind.UNBOX.key();
 300         MethodHandle result = handleMap.get(key);
 301         if (result == null) {
 302             result = MethodHandleBuilder.loadCode(lookup, "unbox" + sourceClass().getName(), MethodType.methodType(valueClass(), boxClass()),
 303                     C -> {
 304                         C.load(0).vunbox(valueClass()).vreturn();
 305                     });
 306             handleMap.put(key, result);
 307         }
 308         return result;
 309     }
 310 
 311     public MethodHandle box() {
 312         ValueHandleKey key = ValueHandleKind.BOX.key();
 313         MethodHandle result = handleMap.get(key);
 314         if (result == null) {
 315             result = MethodHandleBuilder.loadCode(lookup, "box" + sourceClass().getName(), MethodType.methodType(boxClass(), valueClass()),
 316                     C -> {
 317                         C.vload(0).vbox(boxClass()).areturn();
 318                     });
 319             handleMap.put(key, result);
 320         }
 321         return result;
 322     }
 323 
 324     public MethodHandle newArray() {
 325         Class<?> arrayValueClass = arrayValueClass();
 326         ValueHandleKey key = ValueHandleKind.NEWARRAY.key();
 327         MethodHandle result = handleMap.get(key);
 328         if (result == null) {
 329             result = MethodHandleBuilder.loadCode(lookup, "newArray" + sourceClass().getName(), MethodType.methodType(arrayValueClass, int.class),
 330                     C -> {
 331                         C.load(0).anewarray(valueClass()).areturn();
 332                     });
 333             handleMap.put(key, result);
 334         }
 335         return result;
 336     }
 337 
 338     public MethodHandle arrayGetter() {
 339         Class<?> arrayValueClass = arrayValueClass();
 340         ValueHandleKey key = ValueHandleKind.VALOAD.key();
 341         MethodHandle result = handleMap.get(key);
 342         if (result == null) {
 343             result = MethodHandleBuilder.loadCode(lookup, "arrayGet" + sourceClass().getName(), MethodType.methodType(valueClass(), arrayValueClass, int.class),
 344                     C -> {
 345                         C.load(0).load(1).vaload().vreturn();
 346                     });
 347             handleMap.put(key, result);
 348         }
 349         return result;
 350     }
 351 
 352     public MethodHandle arraySetter() {
 353         Class<?> arrayValueClass = arrayValueClass();
 354         ValueHandleKey key = ValueHandleKind.VASTORE.key();
 355         MethodHandle result = handleMap.get(key);
 356         if (result == null) {
 357             result = MethodHandleBuilder.loadCode(lookup, "arraySet" + sourceClass().getName(), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()),
 358                     C -> {
 359                         C.load(0).load(1).load(2).vastore().return_();
 360                     });
 361             handleMap.put(key, result);
 362         }
 363         return result;
 364     }
 365 
 366     public MethodHandle newMultiArray(int dims) {
 367         Class<?> arrayValueClass = arrayValueClass(dims);
 368         ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims);
 369         MethodHandle result = handleMap.get(key);
 370         Class<?>[] params = new Class<?>[dims];
 371         Arrays.fill(params, int.class);
 372         if (result == null) {
 373             result = MethodHandleBuilder.loadCode(lookup, "newMultiArray" + sourceClass().getName(), MethodType.methodType(arrayValueClass, params),
 374                     C -> {
 375                         for (int i = 0 ; i < dims ; i++) {
 376                             C.load(i);
 377                         }
 378                         C.multianewarray(arrayValueClass, (byte)dims).areturn();
 379                     });
 380             handleMap.put(key, result);
 381         }
 382         return result;
 383     }
 384 
 385     private Field[] valueFields() {
 386         int valFieldMask = Modifier.PUBLIC | Modifier.FINAL;
 387         return Stream.of(sourceClass().getDeclaredFields())
 388                 .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask)
 389                 .toArray(Field[]::new);
 390     }
 391 }