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