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 }