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 }