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 //Todo: when 'vwithfield' is ready, this handle could be greatly simplified 273 public MethodHandle findWither(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 274 ValueHandleKey key = ValueHandleKind.WITHER.key(List.of(name, type)); 275 MethodHandle result = handleMap.get(key); 276 if (result == null) { 277 MethodHandle mh = boxLookup.findGetter(boxClass(), name, type); 278 Field field = MethodHandles.reflectAs(Field.class, mh); 279 Class<?> erasedType = type.isPrimitive() ? 280 type : Object.class; 281 Method unsafeMethod = Stream.of(UNSAFE.getClass().getDeclaredMethods()) 282 .filter(m -> m.getName().startsWith("put") && 283 Arrays.asList(m.getParameterTypes()).equals(Arrays.asList(Object.class, long.class, erasedType))) 284 .findFirst().get(); 285 long fieldOffset = UNSAFE.objectFieldOffset(field); 286 result = MethodHandleBuilder.loadCode(boxLookup, mhName("wither$" + name), MethodType.methodType(valueClass(), MethodHandle.class, valueClass(), type), 287 C -> { 288 C.withLocal("boxedVal", BytecodeDescriptor.unparse(boxClass())) 289 .load(1) 290 .vbox(boxClass()) 291 .store("boxedVal") 292 .load(0) 293 .load("boxedVal") 294 .const_(fieldOffset) 295 .load(2); 296 MethodType unsafeMT = MethodType.methodType(unsafeMethod.getReturnType(), unsafeMethod.getParameterTypes()); 297 C.invokevirtual(MethodHandle.class, "invokeExact", BytecodeDescriptor.unparse(unsafeMT), false) 298 .load("boxedVal") 299 .vunbox(valueClass()) 300 .vreturn(); 301 }).bindTo(MethodHandles.lookup().unreflect(unsafeMethod).bindTo(UNSAFE)); 302 handleMap.put(key, result); 303 } 304 //force access-check 305 lookup.findGetter(boxClass(), name, type); 306 return result; 307 } 308 309 public MethodHandle unbox() { 310 ValueHandleKey key = ValueHandleKind.UNBOX.key(); 311 MethodHandle result = handleMap.get(key); 312 if (result == null) { 313 result = MethodHandleBuilder.loadCode(boxLookup, mhName("unbox"), MethodType.methodType(valueClass(), boxClass()), 314 C -> { 315 C.load(0).vunbox(valueClass()).vreturn(); 316 }); 317 handleMap.put(key, result); 318 } 319 return result; 320 } 321 322 public MethodHandle box() { 323 ValueHandleKey key = ValueHandleKind.BOX.key(); 324 MethodHandle result = handleMap.get(key); 325 if (result == null) { 326 result = MethodHandleBuilder.loadCode(boxLookup, mhName("box"), MethodType.methodType(boxClass(), valueClass()), 327 C -> { 328 C.vload(0).vbox(boxClass()).areturn(); 329 }); 330 handleMap.put(key, result); 331 } 332 return result; 333 } 334 335 public MethodHandle newArray() { 336 Class<?> arrayValueClass = arrayValueClass(); 337 ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); 338 MethodHandle result = handleMap.get(key); 339 if (result == null) { 340 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newArray"), MethodType.methodType(arrayValueClass, int.class), 341 C -> { 342 C.load(0).anewarray(valueClass()).areturn(); 343 }); 344 handleMap.put(key, result); 345 } 346 return result; 347 } 348 349 public MethodHandle arrayGetter() { 350 Class<?> arrayValueClass = arrayValueClass(); 351 ValueHandleKey key = ValueHandleKind.VALOAD.key(); 352 MethodHandle result = handleMap.get(key); 353 if (result == null) { 354 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayGet"), MethodType.methodType(valueClass(), arrayValueClass, int.class), 355 C -> { 356 C.load(0).load(1).vaload().vreturn(); 357 }); 358 handleMap.put(key, result); 359 } 360 return result; 361 } 362 363 public MethodHandle arraySetter() { 364 Class<?> arrayValueClass = arrayValueClass(); 365 ValueHandleKey key = ValueHandleKind.VASTORE.key(); 366 MethodHandle result = handleMap.get(key); 367 if (result == null) { 368 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arraySet"), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()), 369 C -> { 370 C.load(0).load(1).load(2).vastore().return_(); 371 }); 372 handleMap.put(key, result); 373 } 374 return result; 375 } 376 377 public MethodHandle newMultiArray(int dims) { 378 Class<?> arrayValueClass = arrayValueClass(dims); 379 ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); 380 MethodHandle result = handleMap.get(key); 381 Class<?>[] params = new Class<?>[dims]; 382 Arrays.fill(params, int.class); 383 if (result == null) { 384 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newMultiArray"), MethodType.methodType(arrayValueClass, params), 385 C -> { 386 for (int i = 0 ; i < dims ; i++) { 387 C.load(i); 388 } 389 C.multianewarray(arrayValueClass, (byte)dims).areturn(); 390 }); 391 handleMap.put(key, result); 392 } 393 return result; 394 } 395 396 public MethodHandle identity() { 397 ValueHandleKey key = ValueHandleKind.IDENTITY.key(); 398 MethodHandle result = handleMap.get(key); 399 if (result == null) { 400 result = MethodHandleBuilder.loadCode(boxLookup, mhName("identity"), MethodType.methodType(valueClass(), valueClass()), 401 C -> C.vload(0).vreturn()); 402 handleMap.put(key, result); 403 } 404 return result; 405 } 406 407 public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 408 ValueHandleKey key = ValueHandleKind.GETTER.key(List.of(name, type)); 409 MethodHandle result = handleMap.get(key); 410 if (result == null) { 411 String fieldType = BytecodeDescriptor.unparse(type); 412 result = MethodHandleBuilder.loadCode(boxLookup, mhName("getter$" + name), MethodType.methodType(type, valueClass()), 413 C -> C.vload(0).vgetfield(valueClass(), name, fieldType).return_(fieldType)); 414 handleMap.put(key, result); 415 } 416 //force access-check 417 lookup.findGetter(boxClass(), name, type); 418 return result; 419 } 420 421 private Field[] valueFields() { 422 int valFieldMask = Modifier.FINAL; 423 return Stream.of(sourceClass().getDeclaredFields()) 424 .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask) 425 .toArray(Field[]::new); 426 } 427 428 } --- EOF ---