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