1 /*
  2  * Copyright (c) 2024, 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.op;
 27 
 28 import jdk.incubator.code.*;
 29 import java.util.HashMap;
 30 import java.util.List;
 31 import java.util.Map;
 32 import java.util.function.Function;
 33 
 34 /**
 35  * An operation that supports externalization of its content and reconstruction
 36  * via an instance of {@link ExternalizedOp}.
 37  * <p>
 38  * The specific content of an externalizable operation can be externalized to a
 39  * map of {@link #attributes attributes}, and is reconstructed from the
 40  * attributes component of an instance of {@link ExternalizedOp}.
 41  * <p>
 42  * An externalizable operation could be externalized via serialization to
 43  * a textual representation. That textual representation could then be deserialized,
 44  * via parsing, into an instance of {@link ExternalizedOp} from which a new
 45  * externalizable operation can be reconstructed that is identical to one that
 46  * was serialized.
 47  */
 48 public abstract class ExternalizableOp extends Op {
 49 
 50     /**
 51      * An operation's externalized content (a record) that can be utilized to construct an instance
 52      * of an {@link ExternalizableOp} associated with the operation's name.
 53      *
 54      * @param name            the operation name
 55      * @param operands        the list of operands
 56      * @param successors      the list of successors
 57      * @param resultType      the operation result type
 58      * @param attributes      the operation's specific content as an attributes map
 59      * @param bodyDefinitions the list of body builders for building the operation's bodies
 60      * @apiNote Deserializers of operations may utilize this record to construct operations,
 61      * thereby separating the specifics of deserializing from construction.
 62      */
 63     public record ExternalizedOp(String name,
 64                                  List<Value> operands,
 65                                  List<Block.Reference> successors,
 66                                  TypeElement resultType,
 67                                  Map<String, Object> attributes,
 68                                  List<Body.Builder> bodyDefinitions) {
 69 
 70         public ExternalizedOp {
 71             attributes = Map.copyOf(attributes);
 72         }
 73 
 74         /**
 75          * Gets an attribute value from the attributes map, converts the value by applying it
 76          * to mapping function, and returns the result.
 77          *
 78          * <p>If the attribute is a default attribute then this method first attempts to
 79          * get the attribute whose name is the empty string, otherwise if there is no such
 80          * attribute present or the attribute is not a default attribute then this method
 81          * attempts to get the attribute with the given name.
 82          *
 83          * <p>On successfully obtaining the attribute its value is converted by applying the value
 84          * to the mapping function. A {@code null} value is represented by the value
 85          * {@link #NULL_ATTRIBUTE_VALUE}.
 86          *
 87          * <p>If no attribute is present the {@code null} value is applied to the mapping function.
 88          *
 89          * @param name      the attribute name.
 90          * @param isDefault true if the attribute is a default attribute
 91          * @param <T>       the converted attribute value type
 92          * @return the converted attribute value
 93          */
 94         public <T> T extractAttributeValue(String name, boolean isDefault, Function<Object, T> mapper) {
 95             Object value = null;
 96             if (isDefault && attributes.containsKey("")) {
 97                 value = attributes.get("");
 98                 assert value != null;
 99             }
100 
101             if (value == null && attributes.containsKey(name)) {
102                 value = attributes.get(name);
103                 assert value != null;
104             }
105 
106             return mapper.apply(value);
107         }
108 
109         /**
110          * Externalizes an operation's content.
111          * <p>
112          * If the operation is an instanceof {@code ExternalizableOp} then the operation's
113          * specific content is externalized to an attribute map, otherwise the attribute map
114          * is empty.
115          *
116          * @param cc the copy context
117          * @param op the operation
118          * @return the operation's content.
119          */
120         public static ExternalizedOp externalizeOp(CopyContext cc, Op op) {
121             return new ExternalizedOp(
122                     op.opName(),
123                     cc.getValues(op.operands()),
124                     op.successors().stream().map(cc::getSuccessorOrCreate).toList(),
125                     op.resultType(),
126                     op instanceof ExternalizableOp exop ? exop.attributes() : Map.of(),
127                     op.bodies().stream().map(b -> b.copy(cc)).toList()
128             );
129         }
130     }
131 
132     /**
133      * The attribute name associated with the location attribute.
134      */
135     public static final String ATTRIBUTE_LOCATION = "loc";
136 
137     /**
138      * The attribute value that represents the external null value.
139      */
140     public static final Object NULL_ATTRIBUTE_VALUE = new Object();
141 
142     /**
143      * Constructs an operation by copying given operation.
144      *
145      * @param that the operation to copy.
146      * @param cc   the copy context.
147      * @implSpec The default implementation calls the constructor with the operation's name, result type, and a list
148      * values computed, in order, by mapping the operation's operands using the copy context.
149      */
150     protected ExternalizableOp(Op that, CopyContext cc) {
151         super(that, cc);
152     }
153 
154     /**
155      * Constructs an operation with a name, operation result type, and list of operands.
156      *
157      * @param name     the operation name.
158      * @param operands the list of operands, a copy of the list is performed if required.
159      */
160     protected ExternalizableOp(String name, List<? extends Value> operands) {
161         super(name, operands);
162     }
163 
164     /**
165      * Constructs an operation from its external content.
166      *
167      * @param def the operation's external content.
168      * @implSpec This implementation invokes the {@link Op#Op(String, List) constructor}
169      * accepting the non-optional components of the operation's content, {@code name},
170      * and {@code operands}:
171      * <pre> {@code
172      *  this(def.name(), def.operands());
173      * }</pre>
174      */
175     @SuppressWarnings("this-escape")
176     protected ExternalizableOp(ExternalizedOp def) {
177         super(def.name(), def.operands());
178         setLocation(extractLocation(def));
179     }
180 
181     static Location extractLocation(ExternalizedOp def) {
182         Object v = def.attributes().get(ATTRIBUTE_LOCATION);
183         return switch (v) {
184             case String s -> Location.fromString(s);
185             case Location loc -> loc;
186             case null -> null;
187             default -> throw new UnsupportedOperationException("Unsupported location value:" + v);
188         };
189     }
190 
191     /**
192      * Externalizes the operation's specific content as a map of attributes.
193      *
194      * <p>A null attribute value is represented by the constant
195      * value {@link #NULL_ATTRIBUTE_VALUE}.
196      *
197      * @return the operation's attributes, as an unmodifiable map
198      */
199     public Map<String, Object> attributes() {
200         Location l = location();
201         return l == null ? Map.of() : Map.of(ATTRIBUTE_LOCATION, l);
202     }
203 }