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 }