1 package jdk.incubator.code.dialect.java;
2
3 import jdk.incubator.code.Body;
4 import jdk.incubator.code.CodeType;
5 import jdk.incubator.code.Op;
6 import jdk.incubator.code.Value;
7 import jdk.incubator.code.dialect.core.CoreOp;
8 import jdk.incubator.code.internal.ArithmeticAndConvOpImpls;
9
10 import java.lang.invoke.MethodHandles;
11 import java.lang.invoke.VarHandle;
12 import java.lang.reflect.Field;
13 import java.lang.reflect.Modifier;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Optional;
18
19 import static jdk.incubator.code.dialect.java.JavaType.J_L_STRING;
20 import static jdk.incubator.code.dialect.java.JavaType.VOID;
21
22 final class ConstantExpressionEvaluator {
23 private final MethodHandles.Lookup l;
24 private final Map<Value, Object> m = new HashMap<>();
25
26 ConstantExpressionEvaluator(MethodHandles.Lookup l) {
27 this.l = l;
28 }
29
30 <T extends Op & JavaOp.JavaExpression> Optional<Object> evaluate(T op) {
31 try {
32 Object v = this.eval(op);
33 return Optional.ofNullable(v);
34 } catch (ArithmeticAndConvOpImpls.NonConstantExpression e) {
35 return Optional.empty();
36 }
37 }
38
39 Optional<Object> evaluate(Value v) {
40 try {
41 Object o = this.eval(v);
42 return Optional.ofNullable(o);
43 } catch (ArithmeticAndConvOpImpls.NonConstantExpression e) {
44 return Optional.empty();
45 }
46 }
47
48 private Object eval(Op op) {
49 if (m.containsKey(op.result())) {
50 return m.get(op.result());
51 }
52 Object r = switch (op) {
53 case CoreOp.ConstantOp cop when isConstant(cop) -> {
54 Object v = cop.value();
55 yield v instanceof String s ? s.intern() : v;
56 }
57 case CoreOp.VarAccessOp.VarLoadOp varLoadOp when varLoadOp.operands().getFirst() instanceof Op.Result &&
58 isConstant(varLoadOp.varOp()) -> eval(varLoadOp.varOp().initOperand());
59 case JavaOp.ConvOp _ -> {
60 // we expect cast to primitive type
61 var v = eval(op.operands().getFirst());
62 yield ArithmeticAndConvOpImpls.evaluate(op, List.of(v));
63 }
64 case JavaOp.CastOp castOp -> {
65 // we expect cast to String
66 Value operand = castOp.operands().getFirst();
67 if (!castOp.resultType().equals(J_L_STRING) || !operand.type().equals(J_L_STRING)) {
68 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
69 }
70 Object v = eval(operand);
71 if (!(v instanceof String s)) {
72 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
73 }
74 yield s;
75 }
76 case JavaOp.ConcatOp concatOp -> {
77 Object first = eval(concatOp.operands().getFirst());
78 Object second = eval(concatOp.operands().getLast());
79 yield (first.toString() + second).intern();
80 }
81 case JavaOp.FieldAccessOp.FieldLoadOp fieldLoadOp -> {
82 Field field;
83 VarHandle vh;
84 try {
85 field = fieldLoadOp.fieldReference().resolveToField(l);
86 vh = fieldLoadOp.fieldReference().resolveToHandle(l);
87 } catch (ReflectiveOperationException | IllegalArgumentException _) {
88 // we cann't reflectivelly get the field
89 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
90 }
91 // Requirement: the field must be a constant variable.
92 // Current checks:
93 // 1) The field is declared final.
94 // 2) The field type is a primitive or String.
95 // Missing check:
96 // 3) Verify the field is initialized and the initializer is a constant expression.
97 if ((field.getModifiers() & Modifier.FINAL) == 0 ||
98 !isConstantType(fieldLoadOp.fieldReference().type())) {
99 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
100 }
101 if ((field.getModifiers() & Modifier.STATIC) != 0) {
102 Object v;
103 try {
104 v = vh.get();
105 } catch (Throwable t) {
106 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
107 }
108 if (!isConstantValue(v)) {
109 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
110 }
111 yield v instanceof String s ? s.intern() : v;
112 } else {
113 // we can't get the value of an instance field from the model
114 // we need the value of the receiver
115 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
116 }
117 }
118 case JavaOp.ArithmeticOperation _ -> {
119 List<Object> values = op.operands().stream().map(this::eval).toList();
120 yield ArithmeticAndConvOpImpls.evaluate(op, values);
121 }
122 case JavaOp.ConditionalExpressionOp _ -> {
123 boolean p = evalBoolean(op.bodies().get(0));
124 Object t = eval(op.bodies().get(1));
125 Object f = eval(op.bodies().get(2));
126 yield p ? t : f;
127 }
128 case JavaOp.ConditionalAndOp _ -> {
129 boolean left = evalBoolean(op.bodies().get(0));
130 boolean right = evalBoolean(op.bodies().get(1));
131 yield left && right;
132 }
133 case JavaOp.ConditionalOrOp _ -> {
134 boolean left = evalBoolean(op.bodies().get(0));
135 boolean right = evalBoolean(op.bodies().get(1));
136 yield left || right;
137 }
138 default -> throw new ArithmeticAndConvOpImpls.NonConstantExpression();
139 };
140 m.put(op.result(), r);
141 return r;
142 }
143
144 private Object eval(Value v) {
145 if (v.declaringElement() instanceof JavaOp.JavaExpression e) {
146 return eval((Op & JavaOp.JavaExpression) e);
147 }
148 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
149 }
150
151 private Object eval(Body body) throws ArithmeticAndConvOpImpls.NonConstantExpression {
152 if (body.blocks().size() != 1 ||
153 !(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
154 yop.yieldValue() == null ||
155 !isConstantType(yop.yieldValue().type())) {
156 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
157 }
158 return eval(yop.yieldValue());
159 }
160
161 private boolean evalBoolean(Body body) throws ArithmeticAndConvOpImpls.NonConstantExpression {
162 Object eval = eval(body);
163 if (!(eval instanceof Boolean b)) {
164 throw new ArithmeticAndConvOpImpls.NonConstantExpression();
165 }
166 return b;
167 }
168
169 private static boolean isConstant(CoreOp.ConstantOp op) {
170 return isConstantType(op.resultType()) && isConstantValue(op.value());
171 }
172
173 private static boolean isConstant(CoreOp.VarOp op) {
174 // Requirement: the local variable must be a constant variable.
175 // Current checks:
176 // 1) The variable is initialized, and the initializer is a constant expression.
177 // 2) The variable type is a primitive or String.
178 // Missing check:
179 // 3) Ensure the variable is declared final
180 return isConstantType(op.varValueType()) &&
181 !op.isUninitialized() &&
182 // @@@ Add to VarOp
183 op.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
184 }
185
186 private static boolean isConstantValue(Object o) {
187 return switch (o) {
188 case String _ -> true;
189 case Boolean _, Byte _, Short _, Character _, Integer _, Long _, Float _, Double _ -> true;
190 case null, default -> false;
191 };
192 }
193
194 private static boolean isConstantType(CodeType e) {
195 return (e instanceof PrimitiveType && !VOID.equals(e)) || J_L_STRING.equals(e);
196 }
197 }