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