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 ARRAYLENGTH, 72 MULTINEWARRAY() { 73 @Override 74 ValueHandleKey key(Object dims) { 75 return new ValueHandleKey(this, dims); 76 } 77 }, 78 IDENTITY, 79 GETTER() { 80 @Override 81 ValueHandleKey key(Object fieldName) { 82 return new ValueHandleKey(this, fieldName); 83 } 84 }; 85 86 ValueHandleKey key() { 87 return new ValueHandleKey(this, null); 88 } 89 90 ValueHandleKey key(Object optArg) { 91 throw new IllegalStateException(); 92 } 93 94 static class ValueHandleKey { 95 ValueHandleKind kind; 96 Optional<Object> optArg; 97 98 ValueHandleKey(ValueHandleKind kind, Object optArg) { 99 this.kind = kind; 100 this.optArg = Optional.ofNullable(optArg); 101 } 102 103 @Override 104 public boolean equals(Object obj) { 105 if (obj instanceof ValueHandleKey) { 106 ValueHandleKey that = (ValueHandleKey)obj; 107 return Objects.equals(kind, that.kind) && 108 Objects.equals(optArg, that.optArg); 109 } else { 110 return false; 111 } 112 } 113 114 @Override 115 public int hashCode() { 116 return Objects.hashCode(kind) * 31 + Objects.hashCode(optArg); 117 } 118 } 119 } 120 121 private static final Lookup IMPL_LOOKUP; 122 123 static { 124 try { 125 Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP"); 126 f.setAccessible(true); 127 IMPL_LOOKUP = (Lookup)f.get(null); 128 } catch (ReflectiveOperationException ex) { 129 throw new AssertionError(ex); 130 } 131 } 132 133 private static final ConcurrentHashMap<Class<?>, ValueType<?>> BOX_TO_VT = new ConcurrentHashMap<>(); 134 135 public static boolean classHasValueType(Class<?> x) { 136 if (!MinimalValueTypes_1_0.isValueCapable(x)) { 137 return false; 138 } 139 return MinimalValueTypes_1_0.getValueTypeClass(x) != null; 140 } 141 142 @SuppressWarnings("unchecked") 143 public static <T> ValueType<T> forClass(Class<T> x) { 144 if (!MinimalValueTypes_1_0.isValueCapable(x)) { 145 throw new IllegalArgumentException("Class " + x + " not a value capable class"); 146 } 147 148 ValueType<T> vt = (ValueType<T>) BOX_TO_VT.get(x); 149 if (vt != null) { 150 return vt; 151 } 152 153 Class<T> valueClass = (Class<T>) MinimalValueTypes_1_0.getValueTypeClass(x); 154 vt = new ValueType<T>(x, valueClass); 155 ValueType<T> old = (ValueType<T>) BOX_TO_VT.putIfAbsent(x, vt); 156 if (old != null) { 157 vt = old; 158 } 159 return vt; 160 } 161 162 private Lookup boxLookup; 163 private Lookup valueLookup; 164 private Map<ValueHandleKind.ValueHandleKey, MethodHandle> handleMap = new ConcurrentHashMap<>(); 165 166 private ValueType(Class<T> boxClass, Class<T> valueClass) { 167 this.boxLookup = IMPL_LOOKUP.in(boxClass); 168 this.valueLookup = IMPL_LOOKUP.in(valueClass); 169 } 170 171 @SuppressWarnings("unchecked") 172 public Class<T> boxClass() { 173 return (Class<T>)boxLookup.lookupClass(); 174 } 175 176 public Class<?> sourceClass() { 177 return boxClass(); 178 } 179 180 public Class<?> valueClass() { 181 return valueLookup.lookupClass(); 182 } 183 184 public Class<?> arrayValueClass() { 185 return arrayValueClass(1); 186 } 187 188 public Class<?> arrayValueClass(int dims) { 189 String dimsStr = "[[[[[[[[[[[[[[[["; 190 if (dims < 1 || dims > 16) { 191 throw new IllegalArgumentException("cannot create array class for dimension > 16"); 192 } 193 String cn = dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";"; 194 return MinimalValueTypes_1_0.loadValueTypeClass(boxLookup.lookupClass(), cn); 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 283 // Allow access if the lookup class is the VCC or DVT and the lookup 284 // has private access 285 Class<?> lc = lookup.lookupClass(); 286 if (lookup.hasPrivateAccess() && (valueClass() == lc || boxClass() == lc)) { 287 return result; 288 } 289 throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to field %s.%s", 290 lc.getName(), boxClass().getName(), name)); 291 } 292 293 public MethodHandle unbox() { 294 ValueHandleKey key = ValueHandleKind.UNBOX.key(); 295 MethodHandle result = handleMap.get(key); 296 if (result == null) { 297 result = MethodHandleBuilder.loadCode(boxLookup, mhName("unbox"), MethodType.methodType(valueClass(), boxClass()), 298 C -> { 299 C.load(0).vunbox(valueClass()).vreturn(); 300 }); 301 handleMap.put(key, result); 302 } 303 return result; 304 } 305 306 public MethodHandle box() { 307 ValueHandleKey key = ValueHandleKind.BOX.key(); 308 MethodHandle result = handleMap.get(key); 309 if (result == null) { 310 result = MethodHandleBuilder.loadCode(boxLookup, mhName("box"), MethodType.methodType(boxClass(), valueClass()), 311 C -> { 312 C.vload(0).vbox(boxClass()).areturn(); 313 }); 314 handleMap.put(key, result); 315 } 316 return result; 317 } 318 319 public MethodHandle newArray() { 320 Class<?> arrayValueClass = arrayValueClass(); 321 ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); 322 MethodHandle result = handleMap.get(key); 323 if (result == null) { 324 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newArray"), MethodType.methodType(arrayValueClass, int.class), 325 C -> { 326 C.load(0).anewarray(valueClass()).areturn(); 327 }); 328 handleMap.put(key, result); 329 } 330 return result; 331 } 332 333 public MethodHandle arrayGetter() { 334 Class<?> arrayValueClass = arrayValueClass(); 335 ValueHandleKey key = ValueHandleKind.VALOAD.key(); 336 MethodHandle result = handleMap.get(key); 337 if (result == null) { 338 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayGet"), MethodType.methodType(valueClass(), arrayValueClass, int.class), 339 C -> { 340 C.load(0).load(1).vaload().vreturn(); 341 }); 342 handleMap.put(key, result); 343 } 344 return result; 345 } 346 347 public MethodHandle arrayLength() { 348 Class<?> arrayValueClass = arrayValueClass(); 349 ValueHandleKey key = ValueHandleKind.ARRAYLENGTH.key(); 350 MethodHandle result = handleMap.get(key); 351 if (result == null) { 352 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arrayLength"), MethodType.methodType(int.class, arrayValueClass), 353 C -> { 354 C.load(0).arraylength().ireturn(); 355 }); 356 handleMap.put(key, result); 357 } 358 return result; 359 } 360 361 public MethodHandle arraySetter() { 362 Class<?> arrayValueClass = arrayValueClass(); 363 ValueHandleKey key = ValueHandleKind.VASTORE.key(); 364 MethodHandle result = handleMap.get(key); 365 if (result == null) { 366 result = MethodHandleBuilder.loadCode(boxLookup, mhName("arraySet"), MethodType.methodType(void.class, arrayValueClass, int.class, valueClass()), 367 C -> { 368 C.load(0).load(1).load(2).vastore().return_(); 369 }); 370 handleMap.put(key, result); 371 } 372 return result; 373 } 374 375 public MethodHandle newMultiArray(int dims) { 376 Class<?> arrayValueClass = arrayValueClass(dims); 377 ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); 378 MethodHandle result = handleMap.get(key); 379 Class<?>[] params = new Class<?>[dims]; 380 Arrays.fill(params, int.class); 381 if (result == null) { 382 result = MethodHandleBuilder.loadCode(boxLookup, mhName("newMultiArray"), MethodType.methodType(arrayValueClass, params), 383 C -> { 384 for (int i = 0 ; i < dims ; i++) { 385 C.load(i); 386 } 387 C.multianewarray(arrayValueClass, (byte)dims).areturn(); 388 }); 389 handleMap.put(key, result); 390 } 391 return result; 392 } 393 394 public MethodHandle identity() { 395 ValueHandleKey key = ValueHandleKind.IDENTITY.key(); 396 MethodHandle result = handleMap.get(key); 397 if (result == null) { 398 result = MethodHandleBuilder.loadCode(boxLookup, mhName("identity"), MethodType.methodType(valueClass(), valueClass()), 399 C -> C.vload(0).vreturn()); 400 handleMap.put(key, result); 401 } 402 return result; 403 } 404 405 public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 406 ValueHandleKey key = ValueHandleKind.GETTER.key(List.of(name, type)); 407 MethodHandle result = handleMap.get(key); 408 if (result == null) { 409 String fieldType = BytecodeDescriptor.unparse(type); 410 result = MethodHandleBuilder.loadCode(boxLookup, mhName("getter$" + name), MethodType.methodType(type, valueClass()), 411 C -> C.vload(0).vgetfield(valueClass(), name, fieldType).return_(fieldType)); 412 handleMap.put(key, result); 413 } 414 //force access-check 415 lookup.findGetter(boxClass(), name, type); 416 return result; 417 } 418 419 private Field[] valueFields() { 420 int valFieldMask = Modifier.FINAL; 421 return Stream.of(sourceClass().getDeclaredFields()) 422 .filter(f -> (f.getModifiers() & (valFieldMask | Modifier.STATIC)) == valFieldMask) 423 .toArray(Field[]::new); 424 } 425 426 }