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 String dimsStr = "[[[[[[[[[[[[[[[["; 187 if (dims < 1 || dims > 16) { 188 throw new IllegalArgumentException("cannot create array class for dimension > 16"); 189 } 190 String cn = dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";"; 191 return MinimalValueTypes_1_0.loadValueTypeClass(boxLookup.lookupClass(), cn); 192 } 193 194 public String toString() { 195 return "ValueType boxClass=" + boxClass() + " valueClass=" + valueClass(); 196 } 197 198 String mhName(String opName) { 199 return sourceClass().getName() + "_" + opName; 200 } 201 202 public MethodHandle defaultValueConstant() { 203 ValueHandleKey key = ValueHandleKind.DEFAULT.key(); 204 MethodHandle result = handleMap.get(key); 205 if (result == null) { 206 result = MethodHandleBuilder.loadCode(boxLookup, mhName("default"), MethodType.methodType(valueClass()), 207 C -> { 208 C.vdefault(valueClass()).vreturn(); 209 }); 210 handleMap.put(key, result); 211 } 212 return result; 213 } 214 215 public MethodHandle substitutabilityTest() { 216 ValueHandleKey key = ValueHandleKind.EQ.key(); 217 MethodHandle result = handleMap.get(key); 218 if (result == null) { 219 result = MethodHandleBuilder.loadCode(valueLookup, mhName("subTest"), MethodType.methodType(boolean.class, valueClass(), valueClass()), 220 C -> { 221 for (Field f : valueFields()) { 222 String fDesc = BytecodeDescriptor.unparse(f.getType()); 223 C.vload(0).vgetfield(valueClass(), f.getName(), fDesc); 224 C.vload(1).vgetfield(valueClass(), f.getName(), fDesc); 225 if (f.getType().isPrimitive()) { 226 C.ifcmp(fDesc, CondKind.NE, "fail"); 227 } else { 228 C.invokestatic(Objects.class, "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false); 229 C.const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail"); 230 } 231 } 232 C.const_(1); 233 C.ireturn(); 234 C.label("fail"); 235 C.const_(0); 236 C.ireturn(); 237 }); 238 handleMap.put(key, result); 239 } 240 return result; 241 } 242 243 public MethodHandle substitutabilityHashCode() { 244 ValueHandleKey key = ValueHandleKind.HASH.key(); 245 MethodHandle result = handleMap.get(key); 246 if (result == null) { 247 result = MethodHandleBuilder.loadCode(valueLookup, mhName("subHash"), MethodType.methodType(int.class, valueClass()), 248 C -> { 249 C.withLocal("res", "I"); 250 C.const_(1).store("res"); 251 for (Field f : valueFields()) { 252 String desc = BytecodeDescriptor.unparse(f.getType()); 253 C.vload(0).vgetfield(valueClass(), f.getName(), desc); 254 if (f.getType().isPrimitive()) { 255 C.invokestatic(Wrapper.asWrapperType(f.getType()), "hashCode", "(" + desc + ")I", false); 256 } else { 257 C.invokestatic(Objects.class, "hashCode", "(Ljava/lang/Object;)I", false); 258 } 259 C.load("res").const_(31).imul(); 260 C.iadd().store("res"); 261 } 262 C.load("res").ireturn(); 263 }); 264 handleMap.put(key, result); 265 } 266 return result; 267 } 268 269 //Todo: when 'vwithfield' is ready, this handle could be greatly simplified 270 public MethodHandle findWither(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 271 ValueHandleKey key = ValueHandleKind.WITHER.key(List.of(name, type)); 272 MethodHandle result = handleMap.get(key); 273 if (result == null) { 274 MethodHandle mh = boxLookup.findGetter(boxClass(), name, type); 275 Field field = MethodHandles.reflectAs(Field.class, mh); 276 Class<?> erasedType = type.isPrimitive() ? 277 type : Object.class; 278 Method unsafeMethod = Stream.of(UNSAFE.getClass().getDeclaredMethods()) 279 .filter(m -> m.getName().startsWith("put") && 280 Arrays.asList(m.getParameterTypes()).equals(Arrays.asList(Object.class, long.class, erasedType))) 281 .findFirst().get(); 282 long fieldOffset = UNSAFE.objectFieldOffset(field); 283 result = MethodHandleBuilder.loadCode(boxLookup, mhName("wither$" + name), MethodType.methodType(valueClass(), MethodHandle.class, valueClass(), type), 284 C -> { 285 C.withLocal("boxedVal", BytecodeDescriptor.unparse(boxClass())) 286 .load(1) 287 .vbox(boxClass()) 288 .store("boxedVal") 289 .load(0) 290 .load("boxedVal") 291 .const_(fieldOffset) 292 .load(2); 293 MethodType unsafeMT = MethodType.methodType(unsafeMethod.getReturnType(), unsafeMethod.getParameterTypes()); 294 C.invokevirtual(MethodHandle.class, "invokeExact", BytecodeDescriptor.unparse(unsafeMT), false) 295 .load("boxedVal") 296 .vunbox(valueClass()) 297 .vreturn(); 298 }).bindTo(MethodHandles.lookup().unreflect(unsafeMethod).bindTo(UNSAFE)); 299 handleMap.put(key, result); 300 } 301 //force access-check 302 lookup.findGetter(boxClass(), name, type); 303 return result; 304 } 305 306 public MethodHandle unbox() { 307 ValueHandleKey key = ValueHandleKind.UNBOX.key(); 308 MethodHandle result = handleMap.get(key); 309 if (result == null) { 310 result = MethodHandleBuilder.loadCode(boxLookup, mhName("unbox"), MethodType.methodType(valueClass(), boxClass()), 311 C -> { 312 C.load(0).vunbox(valueClass()).vreturn(); 313 }); 314 handleMap.put(key, result); 315 } 316 return result; 317 } 318 319 public MethodHandle box() { 320 ValueHandleKey key = ValueHandleKind.BOX.key(); 321 MethodHandle result = handleMap.get(key); 322 if (result == null) { 323 result = MethodHandleBuilder.loadCode(boxLookup, mhName("box"), MethodType.methodType(boxClass(), valueClass()), 324 C -> { 325 C.vload(0).vbox(boxClass()).areturn(); 326 }); 327 handleMap.put(key, result); 328 } 329 return result; 330 } 331 332 public MethodHandle newArray() { 333 Class<?> arrayValueClass = arrayValueClass(); 334 ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); 335 MethodHandle result = handleMap.get(key); 336 if (result == null) { 337 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newArray"), MethodType.methodType(arrayValueClass, int.class), 338 C -> { 339 C.load(0).anewarray(valueClass()).areturn(); 340 }); 341 handleMap.put(key, result); 342 } 343 return result; 344 } 345 346 public MethodHandle arrayGetter() { 347 Class<?> arrayValueClass = arrayValueClass(); 348 ValueHandleKey key = ValueHandleKind.VALOAD.key(); 349 MethodHandle result = handleMap.get(key); 350 if (result == null) { 351 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayGet"), MethodType.methodType(valueClass(), arrayValueClass, int.class), 352 C -> { 353 C.load(0).load(1).vaload().vreturn(); 354 }); 355 handleMap.put(key, result); 356 } 357 return result; 358 } 359 360 public MethodHandle arraySetter() { 361 Class<?> arrayValueClass = arrayValueClass(); 362 ValueHandleKey key = ValueHandleKind.VASTORE.key(); 363 MethodHandle result = handleMap.get(key); 364 if (result == null) { 365 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arraySet"), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()), 366 C -> { 367 C.load(0).load(1).load(2).vastore().return_(); 368 }); 369 handleMap.put(key, result); 370 } 371 return result; 372 } 373 374 public MethodHandle newMultiArray(int dims) { 375 Class<?> arrayValueClass = arrayValueClass(dims); 376 ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); 377 MethodHandle result = handleMap.get(key); 378 Class<?>[] params = new Class<?>[dims]; 379 Arrays.fill(params, int.class); 380 if (result == null) { 381 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newMultiArray"), MethodType.methodType(arrayValueClass, params), 382 C -> { 383 for (int i = 0 ; i < dims ; i++) { 384 C.load(i); 385 } 386 C.multianewarray(arrayValueClass, (byte)dims).areturn(); 387 }); 388 handleMap.put(key, result); 389 } 390 return result; 391 } 392 393 public MethodHandle identity() { 394 ValueHandleKey key = ValueHandleKind.IDENTITY.key(); 395 MethodHandle result = handleMap.get(key); 396 if (result == null) { 397 result = MethodHandleBuilder.loadCode(boxLookup, mhName("identity"), MethodType.methodType(valueClass(), valueClass()), 398 C -> C.vload(0).vreturn()); 399 handleMap.put(key, result); 400 } 401 return result; 402 } 403 404 public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 405 ValueHandleKey key = ValueHandleKind.GETTER.key(List.of(name, type)); 406 MethodHandle result = handleMap.get(key); 407 if (result == null) { 408 String fieldType = BytecodeDescriptor.unparse(type); 409 result = MethodHandleBuilder.loadCode(boxLookup, mhName("getter$" + name), MethodType.methodType(type, valueClass()), 410 C -> C.vload(0).vgetfield(valueClass(), name, fieldType).return_(fieldType)); 411 handleMap.put(key, result); 412 } 413 //force access-check 414 lookup.findGetter(boxClass(), name, type); 415 return result; 416 } 417 418 private Field[] valueFields() { 419 int valFieldMask = Modifier.FINAL; 420 return Stream.of(sourceClass().getDeclaredFields()) 421 .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask) 422 .toArray(Field[]::new); 423 } 424 425 }