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