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.analysis;
 27 
 28 import jdk.incubator.code.*;
 29 import jdk.incubator.code.op.CoreOp;
 30 import jdk.incubator.code.type.JavaType;
 31 import jdk.incubator.code.type.FunctionType;
 32 import jdk.incubator.code.type.MethodRef;
 33 import jdk.incubator.code.type.PrimitiveType;
 34 import java.util.*;
 35 
 36 /**
 37  * StringConcatTransformer is an {@link java.lang.reflect.code.OpTransformer} that removes concatenation operations
 38  * from blocks and replaces them with equivalent {@link java.lang.StringBuilder} operations. This provides a pathway
 39  * to remove {@link java.lang.reflect.code.op.CoreOp.ConcatOp} for easier lowering to Bytecode.
 40  */
 41 public final class StringConcatTransformer implements OpTransformer {
 42 
 43     private static final JavaType J_L_STRING_BUILDER = JavaType.type(StringBuilder.class);
 44     private static final MethodRef SB_TO_STRING_REF = MethodRef.method(
 45             J_L_STRING_BUILDER, "toString", JavaType.J_L_STRING);
 46 
 47     public StringConcatTransformer() {}
 48 
 49     @Override
 50     public Block.Builder apply(Block.Builder block, Op op) {
 51         switch (op) {
 52             case CoreOp.ConcatOp cz when isRootConcat(cz) -> {
 53                 // Create a string builder and build by traversing tree of operands
 54                 Op.Result builder = block.apply(CoreOp._new(FunctionType.functionType(J_L_STRING_BUILDER)));
 55                 buildFromTree(block, builder, cz);
 56                 // Convert to string
 57                 Value s = block.op(CoreOp.invoke(SB_TO_STRING_REF, builder));
 58                 block.context().mapValue(cz.result(), s);
 59             }
 60             case CoreOp.ConcatOp _ -> {
 61                 // Process later when building from root concat
 62             }
 63             default -> block.op(op);
 64         }
 65         return block;
 66     }
 67 
 68     static boolean isRootConcat(CoreOp.ConcatOp cz) {
 69         // Root of concat tree, zero uses, two or more uses,
 70         // or one use that is not a subsequent concat op
 71         Set<Op.Result> uses = cz.result().uses();
 72         return uses.size() != 1 || !(uses.iterator().next().op() instanceof CoreOp.ConcatOp);
 73     }
 74 
 75     static void buildFromTree(Block.Builder block, Op.Result builder, CoreOp.ConcatOp cz) {
 76         // Process concat op's operands from left to right
 77         buildFromTree(block, builder, cz.operands().get(0));
 78         buildFromTree(block, builder, cz.operands().get(1));
 79     }
 80 
 81     static void buildFromTree(Block.Builder block, Op.Result builder, Value v) {
 82         if (v instanceof Op.Result r &&
 83                 r.op() instanceof CoreOp.ConcatOp cz &&
 84                 r.uses().size() == 1) {
 85             // Node of tree, recursively traverse the operands
 86             buildFromTree(block, builder, cz);
 87         } else {
 88             // Leaf of tree, append value to builder
 89             // Note leaf can be the result of a ConcatOp with multiple uses
 90             block.op(append(block, builder, block.context().getValue(v)));
 91         }
 92     }
 93 
 94     private static Op append(Block.Builder block, Value builder, Value arg) {
 95         // Check if we need to widen unsupported integer types in the StringBuilder API
 96         // Strings are fed in as-is, everything else given as an Object.
 97         TypeElement type = arg.type();
 98         if (type instanceof PrimitiveType) {
 99             //Widen Short and Byte to Int.
100             if (type.equals(JavaType.BYTE) || type.equals(JavaType.SHORT)) {
101                 arg = block.op(CoreOp.conv(JavaType.INT, arg));
102                 type = JavaType.INT;
103             }
104         } else if (!type.equals(JavaType.J_L_STRING)){
105             type = JavaType.J_L_OBJECT;
106         }
107 
108         MethodRef methodDesc = MethodRef.method(J_L_STRING_BUILDER, "append", J_L_STRING_BUILDER, type);
109         return CoreOp.invoke(methodDesc, builder, arg);
110     }
111 
112 
113 }