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