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 }