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 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Number) null));
280 }
281
282 @Reflect
283 static boolean unboxing(Integer n) {
284 return n instanceof int _;
285 }
286
287 @Test
288 void testUnboxing() {
289 FuncOp f = getFuncOp("unboxing");
290 System.out.println(f.toText());
291
292 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
293 System.out.println(lf.toText());
294
295 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
296 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
297 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Integer) null));
298 }
299
300 @Reflect
301 static boolean unboxingWideningPrimitive(Integer n) {
302 return n instanceof long _;
303 }
304
305 @Test
306 void testUnboxingWideningPrimitive() {
307 FuncOp f = getFuncOp("unboxingWideningPrimitive");
308 System.out.println(f.toText());
309
310 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
311 System.out.println(lf.toText());
312
313 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
314 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
315 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Integer) null));
316 }
317
318 @Reflect
319 static boolean wideningReference(String s) {
320 return s instanceof Object _;
321 }
322
323 @Test
324 void testWideningReference() {
325 FuncOp f = getFuncOp("wideningReference");
326 System.out.println(f.toText());
327
328 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
329 System.out.println(lf.toText());
330
331 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Object) null));
332 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, "str"));
333 }
334
335 @Reflect
336 static boolean identityReference(Float f) {
337 return f instanceof Float _;
338 }
339
340 @Test
341 void testIdentityReference() {
342 FuncOp f = getFuncOp("identityReference");
343 System.out.println(f.toText());
344
345 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
346 System.out.println(lf.toText());
347
348 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MAX_VALUE));
349 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MIN_VALUE));
350 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.POSITIVE_INFINITY));
351 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Float.NEGATIVE_INFINITY));
352 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, (Float) null));
353 }
354
355 @Reflect
356 static boolean narrowingReference(Number n) {
357 return n instanceof Double _;
358 }
359
360 @Test
361 void testNarrowingReference() {
362 FuncOp f = getFuncOp("narrowingReference");
363 System.out.println(f.toText());
364
365 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
366 System.out.println(lf.toText());
367
368 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, Float.MAX_VALUE));
369 Assertions.assertEquals(false, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
370 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Double.POSITIVE_INFINITY));
371 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Double.NEGATIVE_INFINITY));
372 }
373
374 @Reflect
375 static boolean wideningPrimitive(int i) {
376 return i instanceof long _;
377 }
378
379 @Test
380 void testWideningPrimitive() {
381 FuncOp f = getFuncOp("wideningPrimitive");
382 System.out.println(f.toText());
383
384 FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
385 System.out.println(lf.toText());
386
387 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MAX_VALUE));
388 Assertions.assertEquals(true, Interpreter.invoke(MethodHandles.lookup(), lf, Integer.MIN_VALUE));
389 }
390
391 private CoreOp.FuncOp getFuncOp(String name) {
392 Optional<Method> om = Stream.of(this.getClass().getDeclaredMethods())
393 .filter(m -> m.getName().equals(name))
394 .findFirst();
395
396 Method m = om.get();
397 return Op.ofMethod(m).get();
398 }
399
400 static FuncOp buildTypePatternModel(JavaType sourceType, JavaType targetType) {
401 // builds the model of:
402 // static boolean f(sourceType a) { return a instanceof targetType _; }
403 return func(sourceType + "_" + targetType, functionType(JavaType.BOOLEAN, sourceType)).body(fblock -> {
404
405 var paramVal = fblock.parameters().get(0);
406
407 var patternVar = fblock.add(var(fblock.add(constant(targetType, defaultValue(targetType)))));
408
409 var pattern = Body.Builder.of(fblock.parentBody(), functionType(JavaOp.Pattern.bindingType(targetType)));
410 pattern.entryBlock().add(core_yield(
411 pattern.entryBlock().add(typePattern(targetType, null))
412 ));
413
414 var match = Body.Builder.of(fblock.parentBody(), functionType(JavaType.VOID, targetType));
415 var binding = match.entryBlock().parameters().get(0);
416 match.entryBlock().add(varStore(patternVar, binding));
417 match.entryBlock().add(core_yield());
418
419 var result = fblock.add(match(paramVal, pattern, match));
420
421 fblock.add(return_(result));
422 });
423 }
424
425 static Object defaultValue(JavaType t) {
426 if (List.of(BYTE, SHORT, CHAR, INT).contains(t)) {
427 return 0;
428 } else if (LONG.equals(t)) {
429 return 0L;
430 } else if (FLOAT.equals(t)) {
431 return 0f;
432 } else if (DOUBLE.equals(t)) {
433 return 0d;
434 } else if (BOOLEAN.equals(t)) {
435 return false;
436 }
437 return null;
438 }
439 }