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 }
--- EOF ---