1 /*
2 * Copyright (c) 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code.internal;
27
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.function.Function;
34
35 import jdk.incubator.code.Block;
36 import jdk.incubator.code.Body;
37 import jdk.incubator.code.Op;
38 import jdk.incubator.code.Value;
39 import jdk.incubator.code.dialect.core.CoreOp;
40 import jdk.incubator.code.dialect.java.JavaOp;
41 import jdk.incubator.code.dialect.java.JavaType;
42 import jdk.incubator.code.dialect.java.PrimitiveType;
43
44 public final class ConstantValueAnalysis {
45 private final Function<Block, List<List<Value>>> incomingArguments;
46 private final Map<Value, Optional<Object>> cache = new HashMap<>();
47
48 public ConstantValueAnalysis(Function<Block, List<List<Value>>> incomingArguments) {
49 this.incomingArguments = incomingArguments;
50 }
51
52 void clearUnknowns() {
53 cache.values().removeIf(Optional::isEmpty);
54 }
55
56 public Optional<Object> evaluate(Value value) {
57 if (value == null
58 || !(value.type() instanceof PrimitiveType && !value.type().equals(JavaType.VOID)
59 || value.type().equals(JavaType.J_L_STRING))) {
60 return Optional.empty();
61 }
62 Optional<Object> cached = cache.get(value);
63 if (cached != null) {
64 return cached;
65 }
66 cache.put(value, Optional.empty());
67 Optional<Object> resultValue = switch (value) {
68 case Block.Parameter parameter -> {
69 List<List<Value>> incoming = incomingArguments.apply(parameter.declaringBlock());
70 if (incoming.isEmpty()) {
71 yield Optional.empty();
72 }
73 Object agreed = null;
74 for (List<Value> arguments : incoming) {
75 Optional<Object> constant = evaluate(arguments.get(parameter.index()));
76 if (constant.isEmpty()) {
77 yield Optional.empty();
78 }
79 Object candidate = constant.orElseThrow();
80 if (agreed != null && !agreed.equals(candidate)) {
81 yield Optional.empty();
82 }
83 agreed = candidate;
84 }
85 yield Optional.of(agreed);
86 }
87 case Op.Result result ->
88 switch (result.op()) {
89 case CoreOp.ConstantOp constant ->
90 Optional.ofNullable(constant.value());
91 case JavaOp.ArithmeticOperation _, JavaOp.ConvOp _ -> {
92 List<Object> operands = new ArrayList<>(result.op().operands().size());
93 for (Value operand : result.op().operands()) {
94 Optional<Object> constant = evaluate(operand);
95 if (constant.isEmpty()) {
96 yield Optional.empty();
97 }
98 operands.add(constant.orElseThrow());
99 }
100 yield Optional.of(ArithmeticAndConvOpImpls.evaluate(result.op(), operands));
101 }
102 case JavaOp.CastOp co when value.type().equals(JavaType.J_L_STRING) ->
103 evaluate(co.operands().getFirst()).filter(String.class::isInstance);
104 case JavaOp.ConditionalExpressionOp co ->
105 yieldValue(co.predicateBody()).filter(Boolean.class::isInstance)
106 .map(v -> yieldValue((Boolean) v ? co.trueBody() : co.falseBody()).orElse(null));
107 case JavaOp.ConditionalOrOp co ->
108 co.bodies().stream().map(this::yieldValue)
109 .filter(v -> v.isEmpty() || v.get().equals(true))
110 .findFirst().orElse(Optional.of(false));
111 case JavaOp.ConditionalAndOp co ->
112 co.bodies().stream().map(this::yieldValue)
113 .filter(v -> v.isEmpty() || v.get().equals(false))
114 .findFirst().orElse(Optional.of(true));
115 default -> Optional.empty();
116 };
117 };
118 cache.put(value, resultValue);
119 return resultValue;
120 }
121
122 public Boolean booleanValue(Value value) {
123 return evaluate(value).filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(null);
124 }
125
126 private Optional<Object> yieldValue(Body body) {
127 return body.blocks().size() == 1 && body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yield
128 ? evaluate(yield.yieldValue())
129 : Optional.empty();
130 }
131 }