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 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 String fieldType = BytecodeDescriptor.unparse(type); 277 278 result = MethodHandleBuilder.loadCode(valueLookup, mhName("wither$" + name), MethodType.methodType(valueClass(), valueClass(), type), 279 C -> C.vload(0).load(1).vwithfield(valueClass(), name, fieldType).vreturn()); 280 handleMap.put(key, result); 281 } 282 // @@@ special access check for read/write access 283 //force access-check 284 lookup.findGetter(boxClass(), name, type); 285 return result; 286 } 287 288 public MethodHandle unbox() { 289 ValueHandleKey key = ValueHandleKind.UNBOX.key(); 290 MethodHandle result = handleMap.get(key); 291 if (result == null) { 292 result = MethodHandleBuilder.loadCode(boxLookup, mhName("unbox"), MethodType.methodType(valueClass(), boxClass()), 293 C -> { 294 C.load(0).vunbox(valueClass()).vreturn(); 295 }); 296 handleMap.put(key, result); 297 } 298 return result; 299 } 300 301 public MethodHandle box() { 302 ValueHandleKey key = ValueHandleKind.BOX.key(); 303 MethodHandle result = handleMap.get(key); 304 if (result == null) { 305 result = MethodHandleBuilder.loadCode(boxLookup, mhName("box"), MethodType.methodType(boxClass(), valueClass()), 306 C -> { 307 C.vload(0).vbox(boxClass()).areturn(); 308 }); 309 handleMap.put(key, result); 310 } 311 return result; 312 } 313 314 public MethodHandle newArray() { 315 Class<?> arrayValueClass = arrayValueClass(); 316 ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); 317 MethodHandle result = handleMap.get(key); 318 if (result == null) { 319 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newArray"), MethodType.methodType(arrayValueClass, int.class), 320 C -> { 321 C.load(0).anewarray(valueClass()).areturn(); 322 }); 323 handleMap.put(key, result); 324 } 325 return result; 326 } 327 328 public MethodHandle arrayGetter() { 329 Class<?> arrayValueClass = arrayValueClass(); 330 ValueHandleKey key = ValueHandleKind.VALOAD.key(); 331 MethodHandle result = handleMap.get(key); 332 if (result == null) { 333 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayGet"), MethodType.methodType(valueClass(), arrayValueClass, int.class), 334 C -> { 335 C.load(0).load(1).vaload().vreturn(); 336 }); 337 handleMap.put(key, result); 338 } 339 return result; 340 } 341 342 public MethodHandle arraySetter() { 343 Class<?> arrayValueClass = arrayValueClass(); 344 ValueHandleKey key = ValueHandleKind.VASTORE.key(); 345 MethodHandle result = handleMap.get(key); 346 if (result == null) { 347 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arraySet"), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()), 348 C -> { 349 C.load(0).load(1).load(2).vastore().return_(); 350 }); 351 handleMap.put(key, result); 352 } 353 return result; 354 } 355 356 public MethodHandle newMultiArray(int dims) { 357 Class<?> arrayValueClass = arrayValueClass(dims); 358 ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); 359 MethodHandle result = handleMap.get(key); 360 Class<?>[] params = new Class<?>[dims]; 361 Arrays.fill(params, int.class); 362 if (result == null) { 363 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newMultiArray"), MethodType.methodType(arrayValueClass, params), 364 C -> { 365 for (int i = 0 ; i < dims ; i++) { 366 C.load(i); 367 } 368 C.multianewarray(arrayValueClass, (byte)dims).areturn(); 369 }); 370 handleMap.put(key, result); 371 } 372 return result; 373 } 374 375 public MethodHandle identity() { 376 ValueHandleKey key = ValueHandleKind.IDENTITY.key(); 377 MethodHandle result = handleMap.get(key); 378 if (result == null) { 379 result = MethodHandleBuilder.loadCode(boxLookup, mhName("identity"), MethodType.methodType(valueClass(), valueClass()), 380 C -> C.vload(0).vreturn()); 381 handleMap.put(key, result); 382 } 383 return result; 384 } 385 386 public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 387 ValueHandleKey key = ValueHandleKind.GETTER.key(List.of(name, type)); 388 MethodHandle result = handleMap.get(key); 389 if (result == null) { 390 String fieldType = BytecodeDescriptor.unparse(type); 391 result = MethodHandleBuilder.loadCode(boxLookup, mhName("getter$" + name), MethodType.methodType(type, valueClass()), 392 C -> C.vload(0).vgetfield(valueClass(), name, fieldType).return_(fieldType)); 393 handleMap.put(key, result); 394 } 395 //force access-check 396 lookup.findGetter(boxClass(), name, type); 397 return result; 398 } 399 400 private Field[] valueFields() { 401 int valFieldMask = Modifier.FINAL; 402 return Stream.of(sourceClass().getDeclaredFields()) 403 .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask) 404 .toArray(Field[]::new); 405 } 406 407 }