1 import jdk.incubator.code.Body;
2 import jdk.incubator.code.Reflect;
3 import jdk.incubator.code.CodeTransformer;
4 import jdk.incubator.code.Op;
5 import jdk.incubator.code.bytecode.BytecodeGenerator;
6 import jdk.incubator.code.dialect.core.CoreOp;
7 import jdk.incubator.code.dialect.java.JavaOp;
8 import jdk.incubator.code.dialect.java.JavaType;
9 import jdk.incubator.code.dialect.java.MethodRef;
10 import jdk.incubator.code.interpreter.Interpreter;
11 import org.junit.jupiter.api.Assertions;
12 import org.junit.jupiter.api.Test;
13 import org.junit.jupiter.params.ParameterizedTest;
14 import org.junit.jupiter.params.provider.MethodSource;
15
16 import java.lang.invoke.MethodHandles;
17 import java.lang.reflect.Method;
18 import java.lang.runtime.ExactConversionsSupport;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.stream.Stream;
22
23 import static jdk.incubator.code.dialect.core.CoreOp.*;
24 import static jdk.incubator.code.dialect.core.CoreType.functionType;
25 import static jdk.incubator.code.dialect.java.JavaOp.match;
26 import static jdk.incubator.code.dialect.java.JavaOp.typePattern;
27 import static jdk.incubator.code.dialect.java.PrimitiveType.*;
28
29 /*
30 * @test
31 * @modules jdk.incubator.code
32 * @run junit TestPrimitiveTypePatterns
33 * @enablePreview
34 */
35
36 public class TestPrimitiveTypePatterns {
37
38 static MethodRef conversionMethodRef(JavaType sourceType, JavaType targetType) {
39 if (SHORT.equals(sourceType) || CHAR.equals(sourceType)) {
40 sourceType = INT;
41 }
42 String n = "is%sTo%sExact".formatted(capitalize(sourceType.toString()), capitalize(targetType.toString()));
43 JavaType c = JavaType.type(ExactConversionsSupport.class);
44 return MethodRef.method(c, n, BOOLEAN, sourceType);
45 }
46
47 static String capitalize(String s) {
48 return s.substring(0, 1).toUpperCase() + s.substring(1);
49 }
50
51 public static Object[][] narrowingPrimitiveAndWideningPrimitiveThatNeedCheck() {
52 return new Object[][]{
53 {JavaType.INT, JavaType.BYTE, new Object[] {
54 Byte.MIN_VALUE - 1, Byte.MIN_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE + 1
55 }},
56 {JavaType.INT, JavaType.SHORT, new Object[] {
57 Short.MIN_VALUE - 1, Short.MIN_VALUE, Short.MAX_VALUE, Short.MAX_VALUE + 1
58 }},
59 {JavaType.INT, JavaType.CHAR, new Object[] {
60 Character.MIN_VALUE - 1, Character.MIN_VALUE, Character.MAX_VALUE, Character.MAX_VALUE + 1
61 }},
62 // (1<<24) + 1 : first int that's not an instanceof float
63 // 1<<31) - (1<<7): largest int that's an instance of float
64 {JavaType.INT, JavaType.FLOAT, new Object[] {
65 1<<24, (1<<24) + 1, (1<<31) - (1<<7), (1<<31) - (1<<7) + 1, Integer.MAX_VALUE, Integer.MIN_VALUE
66 }},
67
68 {JavaType.SHORT, JavaType.BYTE, new Object[]{
69 (short) (Byte.MIN_VALUE - 1), Byte.MIN_VALUE, Byte.MAX_VALUE, (short) (Byte.MAX_VALUE + 1)
70 }},
71 {JavaType.SHORT, JavaType.CHAR, new Object[]{
72 Short.MIN_VALUE, (short) -1, (short) 0, Short.MAX_VALUE
73 }},
74
75 {JavaType.CHAR, JavaType.BYTE, new Object[]{
76 (char) 0, (char) Byte.MAX_VALUE, (char) (Byte.MAX_VALUE + 1)
77 }},
78 {JavaType.CHAR, JavaType.SHORT, new Object[]{
79 (char) 0, (char) Short.MAX_VALUE, (char) (Short.MAX_VALUE + 1)
80 }},
81
82 {JavaType.LONG, JavaType.BYTE, new Object[] {
83 Byte.MIN_VALUE - 1, Byte.MIN_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE + 1
84 }},
85 {JavaType.LONG, JavaType.SHORT, new Object[] {
86 Short.MIN_VALUE - 1, Short.MIN_VALUE, Short.MAX_VALUE, Short.MAX_VALUE + 1
87 }},
88 {JavaType.LONG, JavaType.CHAR, new Object[] {
89 Character.MIN_VALUE - 1, Character.MIN_VALUE, Character.MAX_VALUE, Character.MAX_VALUE + 1
90 }},
91 {JavaType.LONG, JavaType.INT, new Object[] {
92 (long)Integer.MIN_VALUE - 1, Integer.MIN_VALUE, Integer.MAX_VALUE, (long)Integer.MAX_VALUE + 1
93 }},
94 // (1<<24) + 1 : first long that can't be represented as float
95 // (1L<<63) - (1L<<39) : largest long that can be represented as float
96 {JavaType.LONG, JavaType.FLOAT, new Object[] {
97 Long.MIN_VALUE, (1L<<24), (1<<24) + 1, (1L<<63) - (1L<<39), (1L<<63) - (1L<<39) + 1, Long.MAX_VALUE
98 }},
99 // (1L<<53) + 1 : first long that can't be represented as double
100 // (1L<<63) - (1<<10) : largest long that can be represented as double
101 {JavaType.LONG, JavaType.DOUBLE, new Object[] {
102 Long.MIN_VALUE, 1L<<53, (1L<<53) + 1, (1L<<63) - (1<<10), (1L<<63) - (1<<10) + 1, Long.MAX_VALUE
103 }},
104
105 {JavaType.FLOAT, JavaType.BYTE, new Object[] {
106 Byte.MIN_VALUE - 1, Byte.MIN_VALUE, Byte.MAX_VALUE, Byte.MIN_VALUE + 1
107 }},
108 {JavaType.FLOAT, JavaType.SHORT, new Object[] {
109 Short.MIN_VALUE - 1, Short.MIN_VALUE, Short.MAX_VALUE, Short.MAX_VALUE + 1
110 }},
111 {JavaType.FLOAT, JavaType.CHAR, new Object[] {
112 Character.MIN_VALUE - 1, Character.MIN_VALUE, Character.MAX_VALUE, Character.MAX_VALUE + 1
113 }},
114 {JavaType.FLOAT, JavaType.INT, new Object[] {
115 Float.MIN_VALUE, Float.NEGATIVE_INFINITY, 0f, Float.POSITIVE_INFINITY, Float.MAX_VALUE
116 }},
117 {JavaType.FLOAT, JavaType.LONG, new Object[] {
118 Float.MIN_VALUE, Float.NEGATIVE_INFINITY, 0f, Float.POSITIVE_INFINITY, Float.MAX_VALUE
119 }},
120
121 {JavaType.DOUBLE, JavaType.BYTE, new Object[] {
122 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
123 }},
124 {JavaType.DOUBLE, JavaType.SHORT, new Object[] {
125 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
126 }},
127 {JavaType.DOUBLE, JavaType.CHAR, new Object[] {
128 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
129 }},
130 {JavaType.DOUBLE, JavaType.INT, new Object[] {
131 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
132 }},
133 {JavaType.DOUBLE, JavaType.LONG, new Object[] {
134 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
135 }},
136 {JavaType.DOUBLE, JavaType.FLOAT, new Object[] {
137 Double.NEGATIVE_INFINITY, Double.MIN_VALUE, -0d, +0d, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN
138 }}
139
140 };
141 }
142
143 @ParameterizedTest
144 @MethodSource("narrowingPrimitiveAndWideningPrimitiveThatNeedCheck")
145 void testNarrowingPrimitiveAndWideningPrimitiveThatNeedCheck(JavaType sourceType, JavaType targetType, Object[] values) throws Throwable {
146
147 var model = buildTypePatternModel(sourceType, targetType);
148 System.out.println(model.toText());
149
150 var lmodel = model.transform(CodeTransformer.LOWERING_TRANSFORMER);
151 System.out.println(lmodel.toText());
152
153
154 var expectedConvMethod = conversionMethodRef(sourceType, targetType);
155 var actualConvMethod = lmodel.elements()
156 .mapMulti((ce, c) -> {
157 if (ce instanceof JavaOp.InvokeOp op) {
158 c.accept(op.invokeDescriptor());
159 }
160 })
161 .findFirst().orElseThrow();
162 Assertions.assertEquals(expectedConvMethod, actualConvMethod);
163
164 var mh = BytecodeGenerator.generate(MethodHandles.lookup(), lmodel);
165
166 for (Object v : values) {
167 Assertions.assertEquals(mh.invoke(v), Interpreter.invoke(MethodHandles.lookup(), lmodel, v));
168 }
169 }
170
171 @Reflect
172 static boolean identityPrimitive(short s, int i, float f) {
173 return s instanceof short _ && i instanceof int _ && f instanceof float _;
174 }
175
176 @Test
177 void testIdentityPrimitive() {
178 FuncOp f = getFuncOp("identityPrimitive");
179 System.out.println(f.toText());
180
181 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
182 System.out.println(lf.toText());
183 // because it's an identity conversion, we expect no check performed
184 Assertions.assertTrue(lf.elements().noneMatch(e -> e instanceof JavaOp.InvokeOp));
185
186 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf,
187 Short.MAX_VALUE, Integer.MAX_VALUE, Float.MAX_VALUE));
188 }
189
190 @Reflect
191 static boolean wideningNarrowingPrimitive(byte s) {
192 return s instanceof char _;
193 }
194
195 @Test
196 void testWideningNarrowingPrimitive() {
197 FuncOp f = getFuncOp("wideningNarrowingPrimitive");
198 System.out.println(f.toText());
199
200 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
201 System.out.println(lf.toText());
202
203 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Byte.MAX_VALUE));
204 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, Byte.MIN_VALUE));
205 }
206
207 @Reflect
208 static boolean boxing(int s) {
209 return s instanceof Integer _;
210 }
211
212 @Test
213 void testBoxing() {
214 FuncOp f = getFuncOp("boxing");
215 System.out.println(f.toText());
216
217 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
218 System.out.println(lf.toText());
219
220 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
221 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
222 }
223
224 @Reflect
225 static boolean boxingWideningReference(int s) {
226 return s instanceof Number _;
227 }
228
229 @Test
230 void testBoxingWideningReference() {
231 FuncOp f = getFuncOp("boxingWideningReference");
232 System.out.println(f.toText());
233
234 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
235 System.out.println(lf.toText());
236
237 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
238 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
239 }
240
241 @Reflect
242 static boolean narrowingReferenceUnboxing(Number n) {
243 return n instanceof int _;
244 }
245
246 @Test
247 void testNarrowingReferenceUnboxing() {
248 FuncOp f = getFuncOp("narrowingReferenceUnboxing");
249 System.out.println(f.toText());
250
251 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
252 System.out.println(lf.toText());
253
254 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, 1));
255 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (short) 1));
256 }
257
258 @Reflect
259 static boolean unboxing(Integer n) {
260 return n instanceof int _;
261 }
262
263 @Test
264 void testUnboxing() {
265 FuncOp f = getFuncOp("unboxing");
266 System.out.println(f.toText());
267
268 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
269 System.out.println(lf.toText());
270
271 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
272 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
273 }
274
275 @Reflect
276 static boolean unboxingWideningPrimitive(Integer n) {
277 return n instanceof long _;
278 }
279
280 @Test
281 void testUnboxingWideningPrimitive() {
282 FuncOp f = getFuncOp("unboxingWideningPrimitive");
283 System.out.println(f.toText());
284
285 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
286 System.out.println(lf.toText());
287
288 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
289 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
290 }
291
292 @Reflect
293 static boolean wideningReference(String s) {
294 return s instanceof Object _;
295 }
296
297 @Test
298 void testWideningReference() {
299 FuncOp f = getFuncOp("wideningReference");
300 System.out.println(f.toText());
301
302 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
303 System.out.println(lf.toText());
304
305 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Object) null));
306 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, "str"));
307 }
308
309 @Reflect
310 static boolean identityReference(Float f) {
311 return f instanceof Float _;
312 }
313
314 @Test
315 void testIdentityReference() {
316 FuncOp f = getFuncOp("identityReference");
317 System.out.println(f.toText());
318
319 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
320 System.out.println(lf.toText());
321
322 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MAX_VALUE));
323 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MIN_VALUE));
324 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.POSITIVE_INFINITY));
325 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.NEGATIVE_INFINITY));
326 }
327
328 @Reflect
329 static boolean narrowingReference(Number n) {
330 return n instanceof Double _;
331 }
332
333 @Test
334 void testNarrowingReference() {
335 FuncOp f = getFuncOp("narrowingReference");
336 System.out.println(f.toText());
337
338 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
339 System.out.println(lf.toText());
340
341 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MAX_VALUE));
342 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
343 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Double.POSITIVE_INFINITY));
344 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Double.NEGATIVE_INFINITY));
345 }
346
347 @Reflect
348 static boolean wideningPrimitive(int i) {
349 return i instanceof long _;
350 }
351
352 @Test
353 void testWideningPrimitive() {
354 FuncOp f = getFuncOp("wideningPrimitive");
355 System.out.println(f.toText());
356
357 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
358 System.out.println(lf.toText());
359
360 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
361 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
362 }
363
364 private CoreOp.FuncOp getFuncOp(String name) {
365 Optional<Method> om = Stream.of(this.getClass().getDeclaredMethods())
366 .filter(m -> m.getName().equals(name))
367 .findFirst();
368
369 Method m = om.get();
370 return Op.ofMethod(m).get();
371 }
372
373 static FuncOp buildTypePatternModel(JavaType sourceType, JavaType targetType) {
374 // builds the model of:
375 // static boolean f(sourceType a) { return a instanceof targetType _; }
376 return func(sourceType + "_" + targetType, functionType(JavaType.BOOLEAN, sourceType)).body(fblock -> {
377
378 var paramVal = fblock.parameters().get(0);
379
380 var patternVar = fblock.op(var(fblock.op(constant(targetType, defaultValue(targetType)))));
381
382 var pattern = Body.Builder.of(fblock.parentBody(), functionType(JavaOp.Pattern.bindingType(targetType)));
383 pattern.entryBlock().op(core_yield(
384 pattern.entryBlock().op(typePattern(targetType, null))
385 ));
386
387 var match = Body.Builder.of(fblock.parentBody(), functionType(JavaType.VOID, targetType));
388 var binding = match.entryBlock().parameters().get(0);
389 match.entryBlock().op(varStore(patternVar, binding));
390 match.entryBlock().op(core_yield());
391
392 var result = fblock.op(match(paramVal, pattern, match));
393
394 fblock.op(return_(result));
395 });
396 }
397
398 static Object defaultValue(JavaType t) {
399 if (List.of(BYTE, SHORT, CHAR, INT).contains(t)) {
400 return 0;
401 } else if (LONG.equals(t)) {
402 return 0L;
403 } else if (FLOAT.equals(t)) {
404 return 0f;
405 } else if (DOUBLE.equals(t)) {
406 return 0d;
407 } else if (BOOLEAN.equals(t)) {
408 return false;
409 }
410 return null;
411 }
412 }