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 valhalla.shady; 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.Modifier; 34 import java.util.Arrays; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Objects; 38 import java.util.concurrent.ConcurrentHashMap; 39 import java.util.function.Consumer; 40 import java.util.function.Supplier; 41 import java.util.stream.Collectors; 42 import java.util.stream.IntStream; 43 import java.util.stream.Stream; 44 45 import jdk.experimental.bytecode.AnnotationsBuilder.Kind; 46 import jdk.experimental.bytecode.Flag; 47 import jdk.experimental.bytecode.Opcode; 48 import jdk.experimental.value.MethodHandleBuilder.IsolatedMethodBuilder; 49 import jdk.experimental.value.MethodHandleBuilder.MethodHandleCodeBuilder; 50 import jdk.experimental.value.MethodHandleBuilder; 51 import jdk.experimental.bytecode.MacroCodeBuilder.CondKind; 52 import jdk.experimental.bytecode.TypeTag; 53 import sun.invoke.util.BytecodeDescriptor; 54 import sun.invoke.util.Wrapper; 55 import valhalla.shady.ValueTypeHolder.ValueHandleKind.ValueHandleKey; 56 57 // Rough place holder just now... 58 public class ValueTypeHolder<T> { 59 60 enum ValueHandleKind { 61 BOX("box"), 62 UNBOX("unbox"), 63 DEFAULT("defaultValueConstant"), 64 EQ("substitutabilityTest"), 65 HASH("substitutabilityHashCode"), 66 ARRAYLENGTH("arrayLength"), 67 WITHER("findWither", Lookup.class, String.class, Class.class) { 68 @Override 69 ValueHandleKey key(Object fieldName) { 70 return new ValueHandleKey(this, fieldName); 71 } 72 }, 73 UNREFLECT_WITHERS("unreflectWithers", Lookup.class, boolean.class, Field[].class) { 74 @Override 75 ValueHandleKey key(Object fields) { 76 return new ValueHandleKey(this, fields); 77 } 78 }, 79 NEWARRAY("newArray"), 80 VALOAD("arrayGetter"), 81 VASTORE("arraySetter"), 82 MULTINEWARRAY("newMultiArray", int.class) { 83 @Override 84 ValueHandleKey key(Object dims) { 85 return new ValueHandleKey(this, dims); 86 } 87 }, 88 IDENTITY("identity"), 89 GETTER("findGetter", Lookup.class, String.class, Class.class) { 90 @Override 91 ValueHandleKey key(Object fieldName) { 92 return new ValueHandleKey(this, fieldName); 93 } 94 }; 95 96 final MethodHandle handle; 97 98 ValueHandleKind(String handleName, Class<?>... argtypes) { 99 try { 100 this.handle = MethodHandles.lookup().findVirtual(ValueTypeHolder.class, handleName, MethodType.methodType(MethodHandle.class, argtypes)); 101 } catch (ReflectiveOperationException ex) { 102 throw new IllegalArgumentException("Cannot initialize value handle key for: " + handleName); 103 } 104 } 105 106 String handleName() { 107 return MethodHandles.lookup().revealDirect(handle).getName(); 108 } 109 110 MethodType handleType() { 111 return MethodHandles.lookup().revealDirect(handle).getMethodType(); 112 } 113 114 ValueHandleKey key() { 115 return new ValueHandleKey(this, null); 116 } 117 118 ValueHandleKey key(Object optArg) { 119 throw new IllegalStateException(); 120 } 121 122 static class ValueHandleKey { 123 ValueHandleKind kind; 124 Object optArg; 125 126 ValueHandleKey(ValueHandleKind kind, Object optArg) { 127 this.kind = kind; 128 this.optArg = optArg; 129 } 130 131 @Override 132 public boolean equals(Object obj) { 133 if (obj instanceof ValueHandleKey) { 134 ValueHandleKey that = (ValueHandleKey)obj; 135 return Objects.equals(kind, that.kind) && 136 Objects.equals(optArg, that.optArg); 137 } else { 138 return false; 139 } 140 } 141 142 @Override 143 public int hashCode() { 144 return Objects.hashCode(kind) * 31 + Objects.hashCode(optArg); 145 } 146 } 147 } 148 149 private static final Lookup IMPL_LOOKUP; 150 151 static { 152 try { 153 Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP"); 154 f.setAccessible(true); 155 IMPL_LOOKUP = (Lookup)f.get(null); 156 } catch (ReflectiveOperationException ex) { 157 throw new AssertionError(ex); 158 } 159 } 160 161 public static <T> Class<T> makeValueTypeClass(Lookup lookup, String name, String[] fieldNames, Class<?>... fieldTypes) throws ReflectiveOperationException { 162 if (fieldNames.length != fieldTypes.length) { 163 throw new IllegalArgumentException("Field names length and field types length must match"); 164 } 165 if (!(fieldNames.length > 0)) { 166 throw new IllegalArgumentException("Field length must be greater than zero"); 167 } 168 IsolatedMethodBuilder builder = new IsolatedMethodBuilder(name, lookup); 169 builder.withMajorVersion(53) 170 .withMinorVersion(0) 171 .withSuperclass(Object.class) 172 .withFlags(Flag.ACC_FINAL) 173 .withAnnotation(Kind.RUNTIME_VISIBLE, MinimalValueTypes_1_0.DERIVE_VALUE_TYPE_DESC); 174 //add fields 175 for (int i = 0 ; i < fieldNames.length ; i++) { 176 builder.withField(fieldNames[i], BytecodeDescriptor.unparse(fieldTypes[i]), F -> F.withFlags(Flag.ACC_FINAL)); 177 } 178 //add constructor 179 String ctype = BytecodeDescriptor.unparseMethod(void.class, fieldTypes); 180 builder.withMethod("<init>", ctype, M -> M.withCode(MethodHandleCodeBuilder::new, C -> { 181 C.aload_0().invokespecial(Object.class, "<init>", "()V", false); 182 int l = 1; 183 for (int i = 0 ; i < fieldNames.length ; i++) { 184 String fType = BytecodeDescriptor.unparse(fieldTypes[i]); 185 C.aload_0().load(l).putfield(builder.thisClass(), fieldNames[i], fType); 186 l += Wrapper.forBasicType(fieldTypes[i]).stackSlots(); 187 } 188 C.return_(); 189 })); 190 //add equals and hashCode 191 builder.withMethod("equals", "(Ljava/lang/Object;)Z", M -> 192 M.withFlags(Flag.ACC_PUBLIC).withCode(MethodHandleCodeBuilder::new, 193 C -> substitutabilityTestBuilder(true, builder.thisClass(), FieldInfo.stream(fieldNames, fieldTypes), C))); 194 builder.withMethod("hashCode", "()I", M -> 195 M.withFlags(Flag.ACC_PUBLIC).withCode(MethodHandleCodeBuilder::new, 196 C -> substitutabilityHashCodeBuilder(builder.thisClass(), FieldInfo.stream(fieldNames, fieldTypes), C))); 197 byte[] barr = builder.build(); 198 MinimalValueTypes_1_0.maybeDump(name, barr); 199 @SuppressWarnings("unchecked") 200 Class<T> vtClass = (Class<T>)lookup.defineClass(barr); 201 return vtClass; 202 } 203 204 private Lookup boxLookup; 205 private Lookup valueLookup; 206 private Map<ValueHandleKind.ValueHandleKey, MethodHandle> handleMap = new ConcurrentHashMap<>(); 207 208 ValueTypeHolder(Class<T> boxClass, Class<T> valueClass) { 209 this.boxLookup = IMPL_LOOKUP.in(boxClass); 210 this.valueLookup = IMPL_LOOKUP.in(valueClass); 211 } 212 213 @SuppressWarnings("unchecked") 214 public Class<T> boxClass() { 215 return (Class<T>)boxLookup.lookupClass(); 216 } 217 218 public Class<?> sourceClass() { 219 return boxClass(); 220 } 221 222 public Class<?> valueClass() { 223 return valueLookup.lookupClass(); 224 } 225 226 public Class<?> arrayValueClass() { 227 return arrayValueClass(1); 228 } 229 230 public Class<?> arrayValueClass(int dims) { 231 String dimsStr = "[[[[[[[[[[[[[[[["; 232 if (dims < 1 || dims > 16) { 233 throw new IllegalArgumentException("cannot create array class for dimension > 16"); 234 } 235 String cn = dimsStr.substring(0, dims) + "Q" + valueClass().getName() + ";"; 236 return MinimalValueTypes_1_0.loadValueTypeClass(boxLookup.lookupClass(), cn); 237 } 238 239 public String toString() { 240 return "ValueType boxClass=" + boxClass() + " valueClass=" + valueClass(); 241 } 242 243 public MethodHandle defaultValueConstant() { 244 ValueHandleKey key = ValueHandleKind.DEFAULT.key(); 245 return getOrLoad(boxLookup, key, 246 () -> MethodType.methodType(valueClass()), 247 C -> C.vdefault(valueClass()).vreturn()); 248 } 249 250 public MethodHandle substitutabilityTest() { 251 ValueHandleKey key = ValueHandleKind.EQ.key(); 252 return getOrLoad(valueLookup, key, 253 () -> MethodType.methodType(boolean.class, valueClass(), valueClass()), 254 C -> substitutabilityTestBuilder(false, valueClass(), FieldInfo.stream(valueFields()), C)); 255 } 256 257 private static <T extends MethodHandleCodeBuilder<T>> void substitutabilityTestBuilder(boolean needsInstanceCheck, Class<?> clazz, Stream<FieldInfo> fInfos, MethodHandleCodeBuilder<T> C) { 258 if (needsInstanceCheck) { 259 C.aload_1() 260 .instanceof_(clazz) 261 .emitCondJump(Opcode.IFEQ, CondKind.EQ, "fail") 262 .aload_1() 263 .checkcast(clazz) 264 .store(1); 265 } 266 fInfos.forEach(fInfo -> { 267 String fDesc = BytecodeDescriptor.unparse(fInfo.getType()); 268 if (fInfo.getType().isPrimitive()) { 269 //field is a primitive type - perform bytecode comparison 270 C.load(0).getfield(clazz, fInfo.getName(), fDesc); 271 C.load(1).getfield(clazz, fInfo.getName(), fDesc); 272 C.ifcmp(fDesc, CondKind.NE, "fail"); 273 } else if (MinimalValueTypes_1_0.isValueType(fInfo.getType())) { 274 //field is a value type - call subst handle recursively 275 C.load(0).getfield(clazz, fInfo.getName(), fDesc).dup().store(2); 276 valueHandleBuilder(fInfo.getType(), ValueHandleKind.EQ.key(), C) 277 .load(2) 278 .load(1).getfield(clazz, fInfo.getName(), fDesc) 279 .invokevirtual(MethodHandle.class, "invoke", 280 MethodType.methodType(boolean.class, fInfo.getType(), fInfo.getType()).toMethodDescriptorString(), false) 281 .const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail"); 282 } else { 283 //otherwise, field is a reference type, fallback to Objects.equals 284 C.load(0).getfield(clazz, fInfo.getName(), fDesc); 285 C.load(1).getfield(clazz, fInfo.getName(), fDesc); 286 C.invokestatic(Objects.class, "equals", "(Ljava/lang/Object;Ljava/lang/Object;)Z", false); 287 C.const_(0).ifcmp(TypeTag.I, CondKind.EQ, "fail"); 288 } 289 }); 290 C.const_(1); 291 C.ireturn(); 292 C.label("fail"); 293 C.const_(0); 294 C.ireturn(); 295 } 296 297 public MethodHandle substitutabilityHashCode() { 298 ValueHandleKey key = ValueHandleKind.HASH.key(); 299 return getOrLoad(valueLookup, key, 300 () -> MethodType.methodType(int.class, valueClass()), 301 C -> substitutabilityHashCodeBuilder(valueClass(), FieldInfo.stream(valueFields()), C)); 302 } 303 304 private static <T extends MethodHandleCodeBuilder<T>> void substitutabilityHashCodeBuilder(Class<?> clazz, Stream<FieldInfo> fInfos, MethodHandleCodeBuilder<T> C) { 305 C.withLocal("res", "I"); 306 C.const_(1).store("res"); 307 fInfos.forEach(fInfo -> { 308 String desc = BytecodeDescriptor.unparse(fInfo.getType()); 309 if (fInfo.getType().isPrimitive()) { 310 C.load(0).getfield(clazz, fInfo.getName(), desc); 311 C.invokestatic(Wrapper.asWrapperType(fInfo.getType()), "hashCode", "(" + desc + ")I", false); 312 } else if (MinimalValueTypes_1_0.isValueType(fInfo.getType())) { 313 //field is a value type - call subst handle recursively 314 C.load(0).getfield(clazz, fInfo.getName(), desc).dup().store(2); 315 valueHandleBuilder(fInfo.getType(), ValueHandleKind.HASH.key(), C) 316 .load(2) 317 .invokevirtual(MethodHandle.class, "invoke", 318 MethodType.methodType(int.class, fInfo.getType()).toMethodDescriptorString(), false); 319 } else { 320 C.load(0).getfield(clazz, fInfo.getName(), desc); 321 C.invokestatic(Objects.class, "hashCode", "(Ljava/lang/Object;)I", false); 322 } 323 C.load("res").const_(31).imul(); 324 C.iadd().store("res"); 325 }); 326 C.load("res").ireturn(); 327 } 328 329 // ()Q 330 public MethodHandle findConstructor(Lookup lookup, MethodType type) throws NoSuchMethodException, IllegalAccessException { 331 return MethodHandles.filterReturnValue(lookup.findConstructor(boxClass(), type), unbox()); 332 } 333 334 // (F1, ..., Fn)Q, fromDefault == true 335 // (Q, F1, ..., Fn)Q, fromDefault == false 336 public MethodHandle unreflectWithers(Lookup lookup, 337 boolean fromDefault, 338 Field... fields) throws NoSuchFieldException, IllegalAccessException { 339 // Allow access if the lookup class is the VCC or DVT and the lookup 340 // has private access 341 Class<?> lc = lookup.lookupClass(); 342 if (!lookup.hasPrivateAccess() || (valueClass() != lc && boxClass() != lc)) { 343 throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to fields of %s", 344 lc.getName(), boxClass().getName())); 345 } 346 347 // Ensure fields are value component fields declared by the VCC 348 for (Field f : fields) { 349 if (!isValueField(f) || f.getDeclaringClass() != sourceClass()) { 350 throw new IllegalArgumentException( 351 String.format("Field %s is not a value component field declared in value capable class %s", f.getName(), sourceClass().getName())); 352 } 353 } 354 355 ValueHandleKey key = ValueHandleKind.UNREFLECT_WITHERS.key(List.of(fromDefault, 356 FieldInfo.stream(fields).collect(Collectors.toList()))); 357 return getOrLoad(valueLookup, key, 358 () -> { 359 MethodType mt = MethodType.methodType( 360 valueClass(), 361 Stream.of(fields).map(Field::getType).toArray(Class[]::new)); 362 363 if (!fromDefault) { 364 mt = mt.insertParameterTypes(0, valueClass()); 365 } 366 return mt; 367 }, 368 C -> { 369 int l = 0; 370 if (fromDefault) { 371 C.vdefault(valueClass()); 372 } else { 373 C.load(0); 374 l = 1; 375 } 376 377 for (Field f : fields) { 378 String fType = BytecodeDescriptor.unparse(f.getType()); 379 C.load(l).vwithfield(valueClass(), f.getName(), fType); 380 l += Wrapper.forBasicType(f.getType()).stackSlots(); 381 } 382 C.vreturn(); 383 }); 384 } 385 386 // (Q, T)Q 387 public MethodHandle findWither(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 388 // Allow access if the lookup class is the VCC or DVT and the lookup 389 // has private access 390 Class<?> lc = lookup.lookupClass(); 391 if (!lookup.hasPrivateAccess() || (valueClass() != lc && boxClass() != lc)) { 392 throw new IllegalAccessException(String.format("Class %s does not have vwithfield access to field %s.%s", 393 lc.getName(), boxClass().getName(), name)); 394 } 395 396 // Check field exists on VCC 397 lookup.findGetter(boxClass(), name, type); 398 399 ValueHandleKey key = ValueHandleKind.WITHER.key(new FieldInfo(name, type)); 400 return getOrLoad(valueLookup, key, 401 () -> MethodType.methodType(valueClass(), valueClass(), type), 402 C -> C.vload(0).load(1).vwithfield(valueClass(), name, BytecodeDescriptor.unparse(type)).vreturn()); 403 } 404 405 public MethodHandle unbox() { 406 ValueHandleKey key = ValueHandleKind.UNBOX.key(); 407 return getOrLoad(boxLookup, key, 408 () -> MethodType.methodType(valueClass(), boxClass()), 409 C -> C.load(0).vunbox(valueClass()).vreturn()); 410 } 411 412 public MethodHandle box() { 413 ValueHandleKey key = ValueHandleKind.BOX.key(); 414 return getOrLoad(boxLookup, key, 415 () -> MethodType.methodType(boxClass(), valueClass()), 416 C -> C.vload(0).vbox(boxClass()).areturn()); 417 } 418 419 public MethodHandle newArray() { 420 ValueHandleKey key = ValueHandleKind.NEWARRAY.key(); 421 return getOrLoad(boxLookup, key, 422 () -> MethodType.methodType(arrayValueClass(), int.class), 423 C -> C.load(0).anewarray(valueClass()).areturn()); 424 } 425 426 public MethodHandle arrayGetter() { 427 ValueHandleKey key = ValueHandleKind.VALOAD.key(); 428 return getOrLoad(boxLookup, key, 429 () -> MethodType.methodType(valueClass(), arrayValueClass(), int.class), 430 C -> C.load(0).load(1).vaload().vreturn()); 431 } 432 433 public MethodHandle arraySetter() { 434 ValueHandleKey key = ValueHandleKind.VASTORE.key(); 435 return getOrLoad(boxLookup, key, 436 () -> MethodType.methodType(void.class, arrayValueClass(), int.class, valueClass()), 437 C -> C.load(0).load(1).load(2).vastore().return_()); 438 } 439 440 public MethodHandle newMultiArray(int dims) { 441 Class<?> arrayValueClass = arrayValueClass(dims); 442 ValueHandleKey key = ValueHandleKind.MULTINEWARRAY.key(dims); 443 return getOrLoad(boxLookup, key, 444 () -> { 445 Class<?>[] params = new Class<?>[dims]; 446 Arrays.fill(params, int.class); 447 return MethodType.methodType(arrayValueClass, params); 448 }, 449 C -> { 450 for (int i = 0 ; i < dims ; i++) { 451 C.load(i); 452 } 453 C.multianewarray(arrayValueClass, (byte)dims).areturn(); 454 }); 455 } 456 457 public MethodHandle arrayLength() { 458 ValueHandleKey key = ValueHandleKind.ARRAYLENGTH.key(); 459 return getOrLoad(boxLookup, key, 460 () -> MethodType.methodType(int.class, arrayValueClass()), 461 C -> C.load(0).arraylength().ireturn()); 462 } 463 464 public MethodHandle identity() { 465 ValueHandleKey key = ValueHandleKind.IDENTITY.key(); 466 return getOrLoad(boxLookup, key, 467 () -> MethodType.methodType(valueClass(), valueClass()), 468 C -> C.vload(0).vreturn()); 469 } 470 471 public MethodHandle findGetter(Lookup lookup, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { 472 //force access-check 473 lookup.findGetter(boxClass(), name, type); 474 475 ValueHandleKey key = ValueHandleKind.GETTER.key(new FieldInfo(name, type)); 476 String fieldType = BytecodeDescriptor.unparse(type); 477 return getOrLoad(boxLookup, key, 478 () -> MethodType.methodType(type, valueClass()), 479 C -> C.vload(0).getfield(valueClass(), name, fieldType).return_(fieldType)); 480 } 481 482 private static <T extends MethodHandleCodeBuilder<T>> T valueHandleBuilder(Class<?> dvt, ValueHandleKey key, MethodHandleCodeBuilder<T> C) { 483 MethodType mt = key.kind.handleType(); 484 if (mt.parameterCount() > 0) { 485 throw new AssertionError("Non-nilary handle builders not supported yet"); 486 } 487 Class<?> vtSupportClass = MinimalValueTypes_1_0.getIncubatorValueTypeClass(); 488 return C.vbox(MinimalValueTypes_1_0.getValueCapableClass(dvt)) 489 .invokevirtual(Object.class, "getClass", "()Ljava/lang/Class;", false) 490 .invokestatic(vtSupportClass, "forClass", 491 MethodType.methodType(vtSupportClass, Class.class).toMethodDescriptorString(), false) 492 .invokevirtual(vtSupportClass, key.kind.handleName(), key.kind.handleType().toMethodDescriptorString(), false); 493 } 494 495 private MethodHandle getOrLoad(Lookup lookup, ValueHandleKey key, Supplier<MethodType> typeSupplier, Consumer<? super MethodHandleCodeBuilder<?>> codeBuilder) { 496 MethodHandle result = handleMap.get(key); 497 if (result == null) { 498 String handleDebugName = sourceClass().getName() + "_" + key.kind.handleName(); 499 result = MethodHandleBuilder.loadCode(lookup, handleDebugName, typeSupplier.get(), codeBuilder); 500 handleMap.put(key, result); 501 } 502 return result; 503 } 504 505 boolean isValueField(Field f) { 506 return (f.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == Modifier.FINAL; 507 } 508 509 public Field[] valueFields() { 510 return Stream.of(sourceClass().getDeclaredFields()) 511 .filter(this::isValueField) 512 .toArray(Field[]::new); 513 } 514 515 final static class FieldInfo { 516 517 private final String name; 518 private final Class<?> type; 519 520 FieldInfo(String name, Class<?> type) { 521 this.name = name; 522 this.type = type; 523 } 524 525 String getName() { return name; } 526 Class<?> getType() { return type; } 527 528 @Override 529 public boolean equals(Object o) { 530 if (o instanceof FieldInfo) { 531 FieldInfo that = (FieldInfo)o; 532 return Objects.equals(name, that.name) && 533 Objects.equals(type, that.type); 534 } else { 535 return false; 536 } 537 } 538 539 @Override 540 public int hashCode() { 541 return Objects.hash(name, type); 542 } 543 544 private static Stream<FieldInfo> stream(Field[] fields) { 545 return Stream.of(fields).map(f -> new FieldInfo(f.getName(), f.getType())); 546 } 547 548 private static Stream<FieldInfo> stream(String[] fieldNames, Class<?>[] fieldTypes) { 549 return IntStream.range(0, fieldNames.length) 550 .mapToObj(i -> new FieldInfo(fieldNames[i], fieldTypes[i])); 551 } 552 } 553 }