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 com.sun.tools.javac.api.JavacScope;
 29 import com.sun.tools.javac.api.JavacTrees;
 30 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 31 import com.sun.tools.javac.comp.Attr;
 32 import com.sun.tools.javac.model.JavacElements;
 33 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
 34 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
 35 import com.sun.tools.javac.tree.TreeMaker;
 36 import com.sun.tools.javac.util.Context;
 37 import jdk.incubator.code.dialect.core.CoreType;
 38 import jdk.incubator.code.dialect.java.JavaOp;
 39 import jdk.incubator.code.internal.ReflectMethods;
 40 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 41 import jdk.incubator.code.dialect.core.FunctionType;
 42 import jdk.incubator.code.dialect.java.MethodRef;
 43 import jdk.incubator.code.extern.OpWriter;
 44 import jdk.internal.access.SharedSecrets;
 45 
 46 import javax.annotation.processing.ProcessingEnvironment;
 47 import javax.lang.model.element.ExecutableElement;
 48 import javax.lang.model.element.Modifier;
 49 import java.lang.reflect.Method;
 50 import java.lang.reflect.Proxy;
 51 import java.util.*;
 52 import java.util.function.BiFunction;
 53 
 54 /**
 55  * An operation modeling a unit of program behavior.
 56  * <p>
 57  * An operation uses zero or more values, exposed as a sequence of {@link #operands()}. A
 58  * {@link Op.Terminating terminating} operation may have block references, exposed as a sequence of
 59  * {@link #successors()}. An operation has zero or more bodies, exposed as a sequence of {@link #bodies()}.
 60  *
 61  * <h2>Operation construction</h2>
 62  * <p>
 63  * An operation is constructed by creating an concrete instance of {@link AbstractOp}. Construction creates an
 64  * <i>unplaced</i> operation. An unplaced operation is not yet part of a code model. The operation's operands,
 65  * successors, bodies, and operation-specific state are fixed when construction completes.
 66  * <p>
 67  * An operation can only be constructed with operands whose declaring block is being built. Otherwise, construction
 68  * fails with an exception.
 69  *
 70  * <h2>Operation building</h2>
 71  * <p>
 72  * Building an operation places an unplaced operation in a code model in one of two ways:
 73  * <ol>
 74  * <li>
 75  * the operation is <i>placed</i> in a block, which becomes its parent block, by using a block builder to
 76  * {@link Block.Builder#add(Op) append} the operation to the block. The placed operation has a permanently
 77  * non-{@code null} {@link #result() result} that can be used as an operand of subsequently constructed operations. The
 78  * block being built is not <a href="Body.Builder.html#body-building-observability">observable</a> through this
 79  * operation and any attempt to access the block throws {@link IllegalStateException}.
 80  * <li>
 81  * the operation is <i>placed</i> as the {@link #isRoot() <i>root operation</i>} of a code model by using
 82  * {@link #buildAsRoot()}. The root operation's {@link #result() result} and {@link #parent() parent} are always
 83  * {@code null}.
 84  * </ol>
 85  * <p>
 86  * Building finishes when the parent body builder of the block in which the operation was placed
 87  * <a href="Body.Builder.html#body-building-finishing">finishes</a>, after which the block becomes observable, or when
 88  * the operation is placed as the root of a code model. After building finishes, the operation's placement and location
 89  * are also fixed, and from then on the operation's observable state does not change.
 90  * <p>
 91  * The {@link #location} may be {@link #setLocation set} while the operation is unplaced or placed in a block whose
 92  * parent body builder has not finished.
 93  * <p>
 94  * An unplaced operation, or an operation placed in a block whose parent body builder has not
 95  * <a href="Body.Builder.html#body-building-finishing">finished</a>, is not thread-safe.
 96  *
 97  * @apiNote
 98  * An operation might model the {@link JavaOp.AddOp addition} of two integers, or a method
 99  * {@link JavaOp.InvokeOp invocation} expression. Alternatively an operation may model something more complex like
100  * {@link jdk.incubator.code.dialect.core.CoreOp.FuncOp method} declarations, {@link JavaOp.LambdaOp lambda}
101  * expressions, or {@link JavaOp.TryOp try} statements. In such cases an operation will contain one or more bodies
102  * modeling the nested structure.
103  */
104 public sealed interface Op extends CodeElement<Op, Body> permits AbstractOp {
105 
106     /**
107      * An operation characteristic indicating the operation is pure and has no side effects.
108      */
109     public interface Pure {
110     }
111 
112     /**
113      * An operation characteristic indicating the operation has one or more bodies.
114      */
115     public interface Nested {
116         /**
117          * {@return a non-empty list of this nested operation's bodies.}
118          */
119         List<Body> bodies();
120     }
121 
122     /**
123      * An operation characteristic indicating the operation represents a loop.
124      */
125     public interface Loop extends Nested {
126         /**
127          * {@return the body of this loop operation.}
128          * <p>
129          * The returned body is one of this operation's {@link #bodies() bodies}.
130          */
131         Body loopBody();
132     }
133 
134     /**
135      * An operation characteristic indicating the operation has one or more bodies,
136      * all of which are isolated and capture no values.
137      */
138     public interface Isolated extends Nested {
139     }
140 
141     /**
142      * An operation characteristic indicating the operation is invokable.
143      */
144     public interface Invokable extends Nested {
145         /**
146          * {@return the body of this invokable operation.}
147          * <p>
148          * The returned body is one of this operation's {@linkplain #bodies() bodies}.
149          */
150         Body body();
151 
152         /**
153          * {@return the invokable operation's signature, represented as a function type.}
154          * @implSpec
155          * The default implementation returns the signature of the invokable operation's body.
156          */
157         default FunctionType invokableSignature() {
158             return body().bodySignature();
159         }
160 
161         /**
162          * {@return the entry block parameters of this operation's body}
163          * @implSpec
164          * The default implementation returns the entry block's parameters of the invokable operation's body.
165          */
166         default List<Block.Parameter> parameters() {
167             return body().entryBlock().parameters();
168         }
169 
170         /**
171          * Computes values captured by this invokable operation's body.
172          *
173          * @return the captured values.
174          * @see Body#capturedValues()
175          */
176         List<Value> capturedValues();
177     }
178 
179     /**
180      * An operation characteristic indicating the operation can lower itself by replacing itself with blocks and
181      * operations that represent the same behavior.
182      */
183     // @@@ Hide this abstraction within JavaOp?
184     public interface Lowerable {
185 
186         /**
187          * Lowers this operation into the given block builder.
188          * <p>
189          * A lowering implementation emits the replacement blocks and operations into the given builder, and returns
190          * the block builder to use for subsequent operations in an enclosing transformation.
191          * <p>
192          * If this operation lowers one of its bodies, it should transform that body with a lowering code transformer
193          * produced by {@link #loweringTransformer(BiFunction, BiFunction)}. This ensures that lowerable operations
194          * encountered in that body are lowered recursively.
195          * The {@code inherited} transformer is the operation transformer inherited from an enclosing lowering, if any.
196          * A lowering implementation may pass it directly to {@code loweringTransformer}, or compose it with another
197          * transformer and pass the composed transformer. The transformer passed to {@code loweringTransformer} is then
198          * supplied as the inherited transformer when that lowering code transformer recursively lowers lowerable
199          * operations.
200          *
201          * @param b the block builder into which this operation is lowered
202          * @param inherited the inherited operation transformer, may be {@code null}
203          * @return the block builder to use for subsequent building
204          */
205         Block.Builder lower(Block.Builder b, BiFunction<Block.Builder, Op, Block.Builder> inherited);
206 
207         /**
208          * Returns a lowering code transformer that partially composes the given operation transformers and, if
209          * required, lowers lowerable operations and appends non-lowerable operations.
210          * <p>
211          * The returned code transformer accepts an operation by first applying the partial composition of
212          * {@code current} with {@code inherited} in the first argument of {@code current}, as if by the following:
213          * {@snippet lang = "java":
214          * Block.Builder composedBlock = inherited == null
215          *         ? block
216          *         : inherited.apply(block, op);
217          * Block.Builder currentBlock = current.apply(composedBlock, op);
218          * }
219          * The returned continuation builder is then selected as if by the following:
220          * {@snippet lang = "java":
221          * if (currentBlock != null) {
222          *     return currentBlock;
223          * } else if (op instanceof Op.Lowerable lop) {
224          *     return lop.lower(composedBlock, inherited);
225          * } else {
226          *     composedBlock.op(op);
227          *     return composedBlock;
228          * }
229          * }
230          *
231          * @param inherited the inherited operation transformer, may be {@code null}
232          * @param current the current operation transformer
233          * @return the lowering code transformer
234          */
235         static CodeTransformer loweringTransformer(BiFunction<Block.Builder, Op, Block.Builder> inherited,
236                                                    BiFunction<Block.Builder, Op, Block.Builder> current) {
237             Objects.requireNonNull(current);
238             return (block, op) -> {
239                 if (inherited != null) {
240                     block = inherited.apply(block, op);
241                 }
242                 Block.Builder currentBlock = current.apply(block, op);
243                 if (currentBlock != null) {
244                     return currentBlock;
245                 } else if (op instanceof Op.Lowerable lop) {
246                     return lop.lower(block, inherited);
247                 } else {
248                     block.add(op);
249                     return block;
250                 }
251             };
252         }
253     }
254 
255     /**
256      * An operation characteristic indicating the operation is a terminating operation
257      * that occurs as the last operation in a block.
258      * <p>
259      * A terminating operation passes control to either another block within the same parent body
260      * or to that parent body.
261      */
262     public interface Terminating {
263     }
264 
265     /**
266      * An operation characteristic indicating the operation is a body-terminating operation
267      * occurring as the last operation in a block.
268      * <p>
269      * A body-terminating operation passes control back to its nearest ancestor body.
270      */
271     public interface BodyTerminating extends Terminating {
272     }
273 
274     /**
275      * An operation characteristic indicating the operation is a block-terminating operation
276      * occurring as the last operation in a block.
277      * <p>
278      * The operation has one or more successors to other blocks within the same parent body, and passes
279      * control to one of those blocks.
280      */
281     public interface BlockTerminating extends Terminating {
282         /**
283          * {@return a non-empty list of this operation's successors.}
284          */
285         List<Block.Reference> successors();
286     }
287 
288     /**
289      * A value that is the result of an operation.
290      */
291     public static final class Result extends Value {
292 
293         /**
294          * If assigned to an operation's result field, indicates the operation is a root operation.
295          */
296         static final Result ROOT_RESULT = new Result();
297 
298         final Op op;
299 
300         private Result() {
301             // Constructor for instance of ROOT_RESULT
302             super(null, null);
303             this.op = null;
304         }
305 
306         Result(Block block, Op op) {
307             super(block, op.resultType());
308 
309             this.op = op;
310         }
311 
312         @Override
313         public String toString() {
314             return "%result@" + Integer.toHexString(hashCode());
315         }
316 
317         @Override
318         public SequencedSet<Value> dependsOn() {
319             SequencedSet<Value> depends = new LinkedHashSet<>(op.operands());
320             if (op instanceof Terminating) {
321                 op.successors().stream().flatMap(h -> h.arguments().stream()).forEach(depends::add);
322             }
323 
324             return Collections.unmodifiableSequencedSet(depends);
325         }
326 
327         /**
328          * {@return the result's declaring operation.}
329          */
330         public Op op() {
331             return op;
332         }
333     }
334 
335     /**
336      * Source location information for an operation.
337      *
338      * @param sourceRef the reference to the source, {@code null} if absent
339      * @param line the line in the source
340      * @param column the column in the source
341      */
342     public record Location(String sourceRef, int line, int column) {
343 
344         /**
345          * The location value, {@code null}, indicating no location information.
346          */
347         public static final Location NO_LOCATION = null;
348 
349         /**
350          * Constructs a location with line and column only.
351          *
352          * @param line the line in the source
353          * @param column the column in the source
354          */
355         public Location(int line, int column) {
356             this(null, line, column);
357         }
358     }
359 
360 
361     /**
362      * Transforms this operation, copying the operation and transforming any of its bodies.
363      * <p>
364      * This method returns a newly constructed, unplaced copy of this operation. The returned operation's concrete
365      * class is the same as this operation's concrete class.
366      * <p>
367      * The returned operation copies this operation's operands, successors, and any operation-specific state. Operands
368      * are copied by mapping this operation's operands, in order, with the given code context. Successors are copied as
369      * specified by {@link CodeContext#getReferenceOrCreate(Block.Reference)}. Operation-specific state is copied as
370      * appropriate for the operation, preserving operation-specific behavior.
371      * <p>
372      * Bodies are {@link Body#transform(CodeContext, CodeTransformer) transformed} with the given code context and code
373      * transformer, and built with the returned operation as their parent.
374      *
375      * @apiNote
376      * To copy an operation use the {@link CodeTransformer#COPYING_TRANSFORMER copying transformer}.
377      *
378      * @param cc the code context
379      * @param ct the code transformer
380      * @return the transformed operation
381      * @see CodeTransformer#COPYING_TRANSFORMER
382      */
383     public Op transform(CodeContext cc, CodeTransformer ct);
384 
385     /**
386      * Sets the originating source location of this operation.
387      *
388      * @param l the location, the {@link Location#NO_LOCATION} value indicates the location is not specified.
389      * @throws IllegalStateException if this operation is a root operation, or is placed in a built block.
390      */
391     public void setLocation(Location l);
392 
393     /**
394      * {@return the originating source location of this operation, otherwise {@code null} if not specified}
395      */
396     public Location location();
397 
398     /**
399      * Returns this operation's parent block, or {@code null} if this operation is unplaced or a root operation.
400      * <p>
401      * The operation's parent block is the same as the operation result's {@link Value#declaringBlock declaring block}.
402      *
403      * @return operation's parent block, or {@code null} if this operation is unplaced or a root operation.
404      * @throws IllegalStateException if this operation is placed in a block that is
405      * <a href="Body.Builder.html#body-building-observability">unobservable</a>.
406      * @see Value#declaringBlock()
407      */
408     @Override
409     public Block parent();
410 
411     /**
412      * {@return the operation's bodies, as an unmodifiable list}
413      * @see #children()
414      */
415     public List<Body> bodies();
416 
417     /**
418      * {@return the operation's result type}
419      */
420     public CodeType resultType();
421 
422     /**
423      * {@return the operation's result, or {@code null} if this operation is unplaced or a
424      * root operation.}
425      */
426     public Result result();
427 
428     /**
429      * {@return the operation's operands, as an unmodifiable list}
430      */
431     public List<Value> operands();
432 
433     /**
434      * {@return the operation's successors, as an unmodifiable list}
435      */
436     public List<Block.Reference> successors();
437 
438     /**
439      * Returns the operation's signature, represented as a function type.
440      * <p>
441      * The signature's return type is the operation's result type and its parameter types are the
442      * operation's operand types, in order.
443      *
444      * @return the operation's signature
445      */
446     public FunctionType opSignature();
447 
448     /**
449      * Computes values captured by this operation. A captured value is a value that is used but not declared by any
450      * descendant operation of this operation.
451      * <p>
452      * The order of the captured values is first use encountered in depth-first search of this operation's descendant
453      * operations.
454      *
455      * @return the list of captured values, modifiable
456      * @see Body#capturedValues()
457      */
458     public List<Value> capturedValues();
459 
460     /**
461      * Builds this operation, placing it as the root operation of a code model. After this operation is placed as a root
462      * operation, its {@link #result() result} and {@link #parent() parent} will always be {@code null}.
463      * <p>
464      * This method is idempotent.
465      *
466      * @throws IllegalStateException if this operation is placed in a block, has any successors,
467      * uses any values as operands, or any of its bodies is open.
468      * @see #isRoot()
469      * @see Body#isIsolated
470      */
471     public void buildAsRoot();
472 
473     /**
474      * {@return {@code true} if this operation is a root operation.}
475      * @see #buildAsRoot()
476      * @see #isPlacedInBlock()
477      */
478     public boolean isRoot();
479 
480     /**
481      * {@return {@code true} if this operation is placed in a block.}
482      * @see #buildAsRoot()
483      * @see #isRoot()
484      */
485     public boolean isPlacedInBlock();
486 
487     /**
488      * Externalizes this operation's name as a string.
489      *
490      * @return the operation name
491      */
492     public String externalizeOpName();
493 
494     /**
495      * Externalizes this operation's specific state as a map of attributes.
496      *
497      * <p>A null attribute value is represented by the constant
498      * value {@link jdk.incubator.code.extern.ExternalizedOp#NULL_ATTRIBUTE_VALUE}.
499      *
500      * @return the operation's externalized state, as an unmodifiable map
501      */
502     public Map<String, Object> externalize();
503 
504     /**
505      * Returns the code model text for this operation.
506      * <p>
507      * The format of code model text is unspecified.
508      *
509      * @return the code model text for this operation.
510      * @apiNote Code model text is designed to be human-readable and is intended for debugging, testing,
511      * and comprehension.
512      * @see OpWriter#toText(Op, OpWriter.Option...)
513      */
514     public String toText();
515 
516 
517     /**
518      * Returns a quoted instance containing the code model of a reflectable lambda expression or method reference.
519      * <p>
520      * The quoted instance also contains a mapping from {@link Value values} in the code model that model final, or
521      * effectively final, variables used but not declared in the lambda expression to their corresponding run time
522      * values. Such run time values are commonly referred to as captured values.
523      * <p>
524      * Repeated invocations of this method will return a quoted instance containing the same instance of the code model.
525      * Therefore, code elements (and more generally code items) contained within the code model can be reliably compared
526      * using object identity.
527      *
528      * @param fiInstance a functional interface instance that is the result of a reflectable lambda expression or
529      *                   method reference.
530      * @return the quoted instance containing the code model, or an empty optional if the functional interface instance
531      * is not the result of a reflectable lambda expression or method reference.
532      * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
533      * model is not the same as the Java version used at runtime to load the code model.
534      * @apiNote if the functional interface instance is a proxy instance, then the quoted code model is unavailable and
535      * this method returns an empty optional.
536      */
537     public static Optional<Quoted<JavaOp.LambdaOp>> ofLambda(Object fiInstance) {
538         Object oq = fiInstance;
539         if (Proxy.isProxyClass(oq.getClass())) {
540             // @@@ The interpreter implements interpretation of
541             // lambdas using a proxy whose invocation handler
542             // supports the internal protocol to access the quoted instance
543             oq = Proxy.getInvocationHandler(oq);
544         }
545 
546         Method method;
547         try {
548             method = oq.getClass().getDeclaredMethod("__internal_quoted");
549         } catch (NoSuchMethodException e) {
550             return Optional.empty();
551         }
552         method.setAccessible(true);
553 
554         Quoted<?> q;
555         try {
556             q = (Quoted<?>) method.invoke(oq);
557         } catch (ReflectiveOperationException e) {
558             // op method may throw UOE in case java compile time version doesn't match runtime version
559             if (e.getCause() instanceof UnsupportedOperationException uoe) {
560                 throw uoe;
561             }
562             throw new RuntimeException(e);
563         }
564         if (!(q.op() instanceof JavaOp.LambdaOp)) {
565             // This can only happen if the stored model is invalid
566             throw new RuntimeException("Invalid code model for lambda expression : " + q);
567         }
568         @SuppressWarnings("unchecked")
569         Quoted<JavaOp.LambdaOp> lq = (Quoted<JavaOp.LambdaOp>) q;
570         return Optional.of(lq);
571     }
572 
573     /**
574      * Returns the code model of a reflectable method.
575      * <p>
576      * Repeated invocations of this method will return the same instance of the code model. Therefore,
577      * code elements (and more generally code items) contained within the code model can be reliably compared using
578      * object identity.
579      *
580      * @param method the method.
581      * @return the code model, or an empty optional if the method is not reflectable.
582      * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
583      * model is not the same as the Java version used at runtime to load the code model.
584      */
585     // @@@ Make caller sensitive with the same access control as invoke
586     // and throwing IllegalAccessException
587     // @CallerSensitive
588     @SuppressWarnings("unchecked")
589     public static Optional<FuncOp> ofMethod(Method method) {
590         return (Optional<FuncOp>)SharedSecrets.getJavaLangReflectAccess()
591                 .setCodeModelIfNeeded(method, Op::createCodeModel);
592     }
593 
594     private static Optional<FuncOp> createCodeModel(Method method) {
595         char[] sig = MethodRef.method(method).toString().toCharArray();
596         for (int i = 0; i < sig.length; i++) {
597             switch (sig[i]) {
598                 case '.', ';', '[', '/': sig[i] = '$';
599             }
600         }
601         String opMethodName = new String(sig);
602         Method opMethod;
603         try {
604             // @@@ Use method handle with full power mode
605             opMethod = method.getDeclaringClass().getDeclaredMethod(opMethodName);
606         } catch (NoSuchMethodException e) {
607             return Optional.empty();
608         }
609         opMethod.setAccessible(true);
610         try {
611             FuncOp funcOp = (FuncOp) opMethod.invoke(null);
612             return Optional.of(funcOp);
613         } catch (ReflectiveOperationException e) {
614             // op method may throw UOE in case java compile time version doesn't match runtime version
615             if (e.getCause() instanceof UnsupportedOperationException uoe) {
616                 throw uoe;
617             }
618             throw new RuntimeException(e);
619         }
620     }
621 
622     /**
623      * Returns the code model of an executable element.
624      * <p>
625      * Repeated invocations of this method will return distinct instances of the code model.
626      *
627      * @param processingEnvironment the annotation processing environment
628      * @param e the executable element.
629      * @return the code model, or an empty optional if the executable element is not reflectable.
630      */
631     public static Optional<FuncOp> ofElement(ProcessingEnvironment processingEnvironment, ExecutableElement e) {
632         if (e.getModifiers().contains(Modifier.ABSTRACT) ||
633                 e.getModifiers().contains(Modifier.NATIVE)) {
634             return Optional.empty();
635         }
636 
637         Context context = ((JavacProcessingEnvironment)processingEnvironment).getContext();
638         ReflectMethods reflectMethods = ReflectMethods.instance(context);
639         Attr attr = Attr.instance(context);
640         JavacElements elements = JavacElements.instance(context);
641         JavacTrees javacTrees = JavacTrees.instance(context);
642         TreeMaker make = TreeMaker.instance(context);
643         try {
644             JCMethodDecl methodTree = (JCMethodDecl)elements.getTree(e);
645             JavacScope scope = javacTrees.getScope(javacTrees.getPath(e));
646             ClassSymbol enclosingClass = (ClassSymbol) scope.getEnclosingClass();
647             FuncOp op = attr.runWithAttributedMethod(scope.getEnv(), methodTree,
648                     attribBlock -> {
649                         try {
650                             return reflectMethods.getMethodBody(enclosingClass, methodTree, attribBlock, make);
651                         } catch (Throwable ex) {
652                             // this might happen if the source code contains errors
653                             return null;
654                         }
655                     });
656             return Optional.ofNullable(op);
657         } catch (RuntimeException ex) {  // ReflectMethods.UnsupportedASTException
658             // some other error occurred when attempting to attribute the method
659             // @@@ better report of error
660             ex.printStackTrace();
661             return Optional.empty();
662         }
663     }
664 }