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 }