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.  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;
 27 
 28 import jdk.incubator.code.dialect.core.CoreOp;
 29 import jdk.incubator.code.dialect.core.CoreType;
 30 import jdk.incubator.code.dialect.core.FunctionType;
 31 import jdk.incubator.code.dialect.core.VarType;
 32 import jdk.incubator.code.dialect.java.JavaType;
 33 import jdk.incubator.code.dialect.java.PrimitiveType;
 34 
 35 import java.util.*;
 36 import java.util.function.Predicate;
 37 
 38 /**
 39  * An operation and a mapping from the operation's {@link Op#operands() operands} and
 40  * {@link Op#capturedValues() captured values} to runtime values. This pair is referred
 41  * to as the quoted form of an operation, and represents an operation in a runtime context
 42  * independent of its surrounding code model.
 43  *
 44  * @param <T> the type of operation that is quoted
 45  */
 46 public final class Quoted<T extends Op> {
 47     private final T op;
 48     private final SequencedMap<Value, Object> operandsAndCapturedValues;
 49 
 50     /**
 51      * Constructs the quoted form of a given operation.
 52      *
 53      * @param op                        the operation.
 54      * @param operandsAndCapturedValues map containing the operation's operands and captured values to runtime values
 55      * @throws IllegalArgumentException if the map's key set does not contain set consisting of operation's operands
 56      *                                  and the captured values
 57      * @see Op#capturedValues()
 58      * @see Op#operands()
 59      */
 60     public Quoted(T op, Map<Value, Object> operandsAndCapturedValues) {
 61         SequencedMap<Value, Object> m = new LinkedHashMap<>();
 62         for (Value value : op.operands()) {
 63             m.put(value, runtimeValue(operandsAndCapturedValues, value, "operand"));
 64         }
 65         for (Value value : op.capturedValues()) {
 66             m.put(value, runtimeValue(operandsAndCapturedValues, value, "captured value"));
 67         }
 68 
 69         this.op = op;
 70         this.operandsAndCapturedValues = Collections.unmodifiableSequencedMap(m);
 71     }
 72 
 73     static Object runtimeValue(Map<Value, Object> operandsAndCapturedValues, Value value, String role) {
 74         if (!operandsAndCapturedValues.containsKey(value)) {
 75             throw new IllegalArgumentException(
 76                     "Cannot create the quoted form of an operation because no runtime value was provided for this "
 77                     + role + "\n"
 78                     + value.block.diagnosticText(value, role + " without runtime value"));
 79         }
 80         return operandsAndCapturedValues.get(value);
 81     }
 82 
 83     /**
 84      * Returns the operation.
 85      *
 86      * @return the operation.
 87      */
 88     public T op() {
 89         return op;
 90     }
 91 
 92     /**
 93      * Returns the map of captured values to runtime values.
 94      * <p>
 95      * The captured values key set has the same elements and same encounter order as
 96      * operation's captured values, specifically the following expression evaluates to true:
 97      * {@snippet lang=java :
 98      * op().capturedValues().equals(new ArrayList<>(capturedValues().keySet()));
 99      * }
100      *
101      * @return the mapping of captured values.
102      */
103     public SequencedMap<Value, Object> capturedValues() {
104         SequencedMap<Value, Object> m = new LinkedHashMap<>();
105         for (Value cv : op.capturedValues()) {
106             m.put(cv, operandsAndCapturedValues.get(cv));
107         }
108         return m;
109     }
110 
111     /**
112      * Returns the map of operands to runtime values.
113      * <p>
114      * The  key set has the same elements and same encounter order as the sequenced set of operation's operands,
115      * specifically the following expression evaluates to true:
116      * {@snippet lang = java:
117      * op.operands().equals(new ArrayList<>(operands().keySet()))
118      *}
119      *
120      * @return the map of operands.
121      */
122     public SequencedMap<Value, Object> operands() {
123         SequencedMap<Value, Object> m = new LinkedHashMap<>();
124         for (Value operand : op.operands()) {
125             // putIfAbsent is used because a value may be used as operand more than once
126             m.putIfAbsent(operand, operandsAndCapturedValues.get(operand));
127         }
128         return m;
129     }
130 
131     /**
132      * Returns the map of operands and captured values to runtime values.
133      * <p>
134      * The map's key set is equal to the sequenced set of the quoted operation's
135      * operands and captured values.
136      *
137      * @return the map of operands and captured values, as an unmodifiable map.
138      */
139     public SequencedMap<Value, Object> operandsAndCapturedValues() {
140         return operandsAndCapturedValues;
141     }
142 
143     /**
144      * Returns a quoting code model whose behavior quotes the given source operation.
145      * <p>
146      * This method behaves as if it returns the result of invoking {@link #embedOp(Op, Map)} with the source operation
147      * and an empty map as the arguments.
148      *
149      * @param op the source operation to quote
150      * @return the quoting code model
151      * @throws IllegalArgumentException if {@code op} is not placed in a block
152      * @see #embedOp(Op, Map)
153      */
154     public static CoreOp.FuncOp embedOp(Op op) {
155         return embedOp(op, Map.of());
156     }
157 
158     /**
159      * Returns a quoting code model whose behavior quotes the given source operation.
160      * <p>
161      * A quoting code model is a {@link jdk.incubator.code.dialect.core.CoreOp.FuncOp function operation} that has one
162      * body with one block, <i>fblock</i>, and whose behavior returns an instance of {@link Quoted} that contains a copy
163      * of the source operation.
164      * <p>
165      * The source operation has a sequence of values, <i>source values</i>, that is the operation's
166      * {@link Op#operands() operands} followed by the operation's {@link Op#capturedValues() captured values}.
167      * The quoting function operation has a sequence of values, <i>quoting values</i>, mapped from the source values.
168      * Each quoting value is a block parameter of <i>fblock</i>, the result of variable operation in <i>fblock</i>
169      * initialized with a block parameter of <i>fblock</i>, or the result of constant operation in <i>fblock</i>.
170      * <p>
171      * For each source value, if the value has an entry in the captured constants, then <i>fblock</i> will contain a
172      * constant operation whose constant value is the entry's value. The source value is mapped to a quoting value that
173      * is the result of the constant operation. Otherwise, if the value has no entry in the captured constants,
174      * <i>fblock</i> has a block parameter in the same order as the source value. If the source value is the result of a
175      * {@link jdk.incubator.code.dialect.core.CoreOp.VarOp variable operation} then the block parameter's code type is
176      * the same as variable operation's value type, and <i>fblock</i> has a variable operation whose operand is the
177      * block parameter. The source value is mapped to a quoting value that is the result of the variable operation.
178      * Otherwise, the block parameter's code type is the same as the source value's code type. The source value is
179      * mapped to a quoting value that is the block parameter.
180      * <p>
181      * <i>fblock</i> has a {@link jdk.incubator.code.dialect.core.CoreOp.QuotedOp quoted operation} that has one body
182      * with one block (<i>qblock</i>). <i>qblock</i> has a copy of the source operation, that is copied using a code
183      * context containing the mappings of source values to quoting values.
184      * <p>
185      * <i>fblock</i> terminates with a {@link jdk.incubator.code.dialect.core.CoreOp.ReturnOp return operation}, whose
186      * operand is the operation result of the quoted operation.
187      *
188      * @param op the source operation to quote
189      * @param capturedConstants the captured constants to embed in the quoting code model
190      * @return the quoting code model
191      * @throws IllegalArgumentException if source operation is not placed in a block
192      */
193     public static CoreOp.FuncOp embedOp(Op op, Map<Value, Object> capturedConstants) {
194         if (op.result() == null) {
195             throw new IllegalArgumentException("Operation is not placed in a block");
196         }
197 
198         // if we don't remove duplicate operands we will have unused params in the new model
199         // if we don't remove captured values that are operands we will have unused params in the new model
200         SequencedSet<Value> s = new LinkedHashSet<>(op.operands());
201         s.addAll(op.capturedValues());
202         List<Value> inputOperandsAndCaptures = s.stream().toList();
203 
204         // Build the function type
205         List<CodeType> params = inputOperandsAndCaptures.stream()
206                 .filter(Predicate.not(capturedConstants::containsKey))
207                 .map(v -> v.type() instanceof VarType vt ? vt.valueType() : v.type())
208                 .toList();
209         FunctionType ft = CoreType.functionType(CoreOp.QuotedOp.QUOTED_OP_TYPE, params);
210 
211         // Build the function that quotes the lambda
212         return CoreOp.func("q", ft).body(b -> {
213             // Create variables as needed and obtain the operands and captured values for the copied lambda
214             List<Value> outputOperandsAndCaptures = new ArrayList<>();
215             int i = 0;
216             for (Value inputValue : inputOperandsAndCaptures) {
217                 Value outputValue;
218                 if (capturedConstants.containsKey(inputValue)) {
219                     outputValue = b.add(CoreOp.constant(inputValue.type(), capturedConstants.get(inputValue)));
220                 } else {
221                     outputValue = b.parameters().get(i++);
222                     if (inputValue.type() instanceof VarType) {
223                         outputValue = b.add(CoreOp.var(String.valueOf(i), outputValue));
224                     }
225                 }
226                 outputOperandsAndCaptures.add(outputValue);
227             }
228 
229             // Quoted the lambda expression
230             Value q = b.add(CoreOp.quoted(b.parentBody(), qb -> {
231                 // Map the entry block of the op's ancestor body to the quoted block
232                 // We are copying op in the context of the quoted block, the block mapping
233                 // ensures the use of operands and captured values are reachable when building
234                 qb.context().mapBlock(op.ancestorBody().entryBlock(), qb);
235                 // Map the op's operands and captured values
236                 qb.context().mapValues(inputOperandsAndCaptures, outputOperandsAndCaptures);
237                 // Return the op to be copied in the quoted operation
238                 return op;
239             }));
240             b.add(CoreOp.return_(q));
241         });
242     }
243 
244     private static IllegalArgumentException invalidQuotedModel(CoreOp.FuncOp model) {
245         return new IllegalArgumentException("Invalid code model for quoted operation : " + model);
246     }
247 
248     /**
249      * Extracts the quoted operation from a quoting code model with the given runtime arguments.
250      * <p>
251      * This method behaves as if the quoting code model is executed with the runtime arguments by interpreting the
252      * behavior of the code elements, as specified, in the code model, but it may be implemented more efficiently.
253      *
254      * @param funcOp the quoting code model
255      * @param args   the runtime arguments
256      * @return quoted form of the extracted operation
257      * @throws IllegalArgumentException if the quoting code model is invalid and does not conform to that specified
258      * the method {@link #embedOp(Op)}.
259      * @throws IllegalArgumentException if the quoting code model's parameters differ in arity from the
260      * runtime arguments.
261      */
262     public static Quoted<Op> extractOp(CoreOp.FuncOp funcOp, List<Object> args) {
263         if (funcOp.body().blocks().size() != 1) {
264             throw invalidQuotedModel(funcOp);
265         }
266         Block fblock = funcOp.body().entryBlock();
267         List<Op> ops = fblock.ops();
268         if (ops.size() < 2
269                 || !(ops.get(ops.size() - 2) instanceof CoreOp.QuotedOp qop)
270                 || !(ops.getLast() instanceof CoreOp.ReturnOp returnOp)
271                 || returnOp.returnValue() == null
272                 || !returnOp.returnValue().equals(qop.result())) {
273 
274             throw invalidQuotedModel(funcOp);
275         }
276 
277         Op op = qop.quotedOp();
278 
279         SequencedSet<Value> operandsAndCaptures = new LinkedHashSet<>();
280         operandsAndCaptures.addAll(op.operands());
281         operandsAndCaptures.addAll(op.capturedValues());
282 
283         // each parameter supplies one quoted value, directly or through an outer VarOp
284         // @@@ unused receiver is hard to identify and must be accepted
285         for (Block.Parameter p : fblock.parameters()) {
286             if (!operandsAndCaptures.contains(p)
287                     && !p.uses().stream().allMatch(u -> u.op() instanceof CoreOp.VarOp varOp
288                     && varOp.ancestorBlock() == fblock
289                     && operandsAndCaptures.contains(varOp.result()))) {
290                 throw invalidQuotedModel(funcOp);
291             }
292         }
293 
294         for (int i = 0; i < ops.size() - 2; i++) {
295             if (ops.get(i) instanceof CoreOp.ConstantOp constantOp
296                     && (constantOp.resultType() instanceof PrimitiveType
297                         && !constantOp.resultType().equals(JavaType.VOID)
298                         || constantOp.resultType().equals(JavaType.J_L_STRING))
299                     && operandsAndCaptures.contains(constantOp.result())) {
300                 continue;
301             } else if (!(ops.get(i) instanceof CoreOp.VarOp varOp)
302                     || varOp.isUninitialized()
303                     || !(varOp.initOperand() instanceof Block.Parameter p)
304                     || p.declaringBlock() != fblock
305                     || !operandsAndCaptures.contains(varOp.result())) {
306                 throw invalidQuotedModel(funcOp);
307             }
308         }
309 
310         // map operands and captures to their corresponding runtime values
311         // operand and capture can be:
312         // 1- block param of this function
313         // 2- result of VarOp whose initial value is a block param of this function
314         // 3- result of a primitive or String ConstantOp in this function's entry block
315         List<Block.Parameter> params = funcOp.parameters();
316         if (params.size() != args.size()) {
317             throw invalidQuotedModel(funcOp);
318         }
319         SequencedMap<Value, Object> m = new LinkedHashMap<>();
320         for (Value v : operandsAndCaptures) {
321             switch (v) {
322                 // reject captures from another block
323                 case Block.Parameter p when p.declaringBlock() == fblock -> {
324                     Object rv = args.get(p.index());
325                     m.put(v, rv);
326                 }
327                 case Op.Result opr when opr.op() instanceof CoreOp.VarOp varOp
328                                      && varOp.ancestorBlock() == fblock
329                                      && varOp.initOperand() instanceof Block.Parameter p -> {
330                     Object rv = args.get(p.index());
331                     m.put(v, CoreOp.Var.of(rv));
332                 }
333                 case Op.Result opr when opr.op() instanceof CoreOp.ConstantOp constantOp
334                                      && constantOp.ancestorBlock() == fblock ->
335                         m.put(v, constantOp.value());
336                 default -> throw invalidQuotedModel(funcOp);
337             }
338         }
339 
340         return new Quoted<>(op, m);
341     }
342 
343     /**
344      * Extracts the quoted operation from {@code funcOp}
345      * and map its operands and captured values to the runtime values in {@code args}.
346      * <p>
347      * {@code funcOp} must have the same structure as if it's produced by {@link #embedOp(Op)}.
348      *
349      * @param funcOp Model to extract the quoted op from
350      * @param args   Runtime values for {@code funcOp} parameters
351      * @return Quoted instance that wraps the quoted operation,
352      * plus the mapping of its operands and captured values to the given runtime values
353      * @throws RuntimeException If {@code funcOp} isn't a valid code model
354      * @throws RuntimeException If {@code funcOp} parameters size is different from {@code args} length
355      * @see Quoted#extractOp(CoreOp.FuncOp, List)
356      */
357     public static Quoted<Op> extractOp(CoreOp.FuncOp funcOp, Object... args) {
358         return extractOp(funcOp, Arrays.asList(args));
359     }
360 }