1 /* 2 * Copyright (c) 2015, 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 package jdk.tools.jlink.internal.plugins; 26 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.io.UncheckedIOException; 31 import java.nio.charset.StandardCharsets; 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Set; 37 import java.util.function.Consumer; 38 import jdk.internal.org.objectweb.asm.ClassReader; 39 import jdk.internal.org.objectweb.asm.ClassWriter; 40 import jdk.internal.org.objectweb.asm.Opcodes; 41 import jdk.tools.jlink.internal.plugins.asm.AsmPools; 42 import jdk.tools.jlink.internal.plugins.asm.AsmPlugin; 43 import jdk.internal.org.objectweb.asm.tree.ClassNode; 44 import jdk.internal.org.objectweb.asm.tree.MethodNode; 45 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; 46 import jdk.tools.jlink.internal.plugins.asm.AsmModulePool; 47 import jdk.tools.jlink.internal.plugins.optim.ForNameFolding; 48 import jdk.tools.jlink.internal.plugins.optim.ReflectionOptimizer.TypeResolver; 49 import jdk.tools.jlink.plugin.PluginException; 50 51 /** 52 * 53 * Optimize Classes following various strategies. Strategies are implementation 54 * of <code>ClassOptimizer</code> and <code>MethodOptimizer</code>. 55 */ 56 public final class OptimizationPlugin extends AsmPlugin { 57 58 public static final String NAME = "class-optim"; 59 public static final String LOG = "log"; 60 public static final String ALL = "all"; 61 public static final String FORNAME_REMOVAL = "forName-folding"; 62 63 /** 64 * Default resolver. A resolver that retrieve types that are in an 65 * accessible package, are public or are located in the same package as the 66 * caller. 67 */ 68 private static final class DefaultTypeResolver implements TypeResolver { 69 70 private final Set<String> packages; 71 private final AsmPools pools; 72 73 DefaultTypeResolver(AsmPools pools, AsmModulePool modulePool) { 74 Objects.requireNonNull(pools); 75 Objects.requireNonNull(modulePool); 76 this.pools = pools; 77 packages = pools.getGlobalPool().getAccessiblePackages(modulePool.getModuleName()); 78 } 79 80 @Override 81 public ClassReader resolve(ClassNode cn, MethodNode mn, String type) { 82 int classIndex = cn.name.lastIndexOf("/"); 83 String callerPkg = classIndex == -1 ? "" 84 : cn.name.substring(0, classIndex); 85 int typeClassIndex = type.lastIndexOf("/"); 86 String pkg = typeClassIndex == - 1 ? "" 87 : type.substring(0, typeClassIndex); 88 ClassReader reader = null; 89 if (packages.contains(pkg) || pkg.equals(callerPkg)) { 90 ClassReader r = pools.getGlobalPool().getClassReader(type); 91 if (r != null) { 92 // if not private 93 if ((r.getAccess() & Opcodes.ACC_PRIVATE) 94 != Opcodes.ACC_PRIVATE) { 95 // public 96 if (((r.getAccess() & Opcodes.ACC_PUBLIC) 97 == Opcodes.ACC_PUBLIC)) { 98 reader = r; 99 } else if (pkg.equals(callerPkg)) { 100 reader = r; 101 } 102 } 103 } 104 } 105 return reader; 106 } 107 } 108 109 public interface Optimizer { 110 111 void close() throws IOException; 112 } 113 114 public interface ClassOptimizer extends Optimizer { 115 116 boolean optimize(Consumer<String> logger, AsmPools pools, 117 AsmModulePool modulePool, 118 ClassNode cn) throws Exception; 119 } 120 121 public interface MethodOptimizer extends Optimizer { 122 123 boolean optimize(Consumer<String> logger, AsmPools pools, 124 AsmModulePool modulePool, 125 ClassNode cn, MethodNode m, TypeResolver resolver) throws Exception; 126 } 127 128 private List<Optimizer> optimizers = new ArrayList<>(); 129 130 private OutputStream stream; 131 private int numMethods; 132 133 private void log(String content) { 134 if (stream != null) { 135 try { 136 content = content + "\n"; 137 stream.write(content.getBytes(StandardCharsets.UTF_8)); 138 } catch (IOException ex) { 139 System.err.println(ex); 140 } 141 } 142 } 143 144 private void close() throws IOException { 145 log("Num analyzed methods " + numMethods); 146 147 for (Optimizer optimizer : optimizers) { 148 try { 149 optimizer.close(); 150 } catch (IOException ex) { 151 System.err.println("Error closing optimizer " + ex); 152 } 153 } 154 if (stream != null) { 155 stream.close(); 156 } 157 } 158 159 @Override 160 public String getName() { 161 return NAME; 162 } 163 164 @Override 165 public void visit(AsmPools pools) { 166 try { 167 for (AsmModulePool p : pools.getModulePools()) { 168 DefaultTypeResolver resolver = new DefaultTypeResolver(pools, p); 169 p.visitClassReaders((reader) -> { 170 ClassWriter w = null; 171 try { 172 w = optimize(pools, p, reader, resolver); 173 } catch (IOException ex) { 174 throw new PluginException("Problem optimizing " 175 + reader.getClassName(), ex); 176 } 177 return w; 178 }); 179 } 180 } finally { 181 try { 182 close(); 183 } catch (IOException ex) { 184 throw new UncheckedIOException(ex); 185 } 186 } 187 } 188 189 private ClassWriter optimize(AsmPools pools, AsmModulePool modulePool, 190 ClassReader reader, TypeResolver resolver) 191 throws IOException { 192 ClassNode cn = new ClassNode(); 193 ClassWriter writer = null; 194 if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) { 195 reader.accept(cn, ClassReader.EXPAND_FRAMES); 196 boolean optimized = false; 197 for (Optimizer optimizer : optimizers) { 198 if (optimizer instanceof ClassOptimizer) { 199 try { 200 boolean optim = ((ClassOptimizer) optimizer). 201 optimize(this::log, pools, modulePool, cn); 202 if (optim) { 203 optimized = true; 204 } 205 } catch (Throwable ex) { 206 throw new PluginException("Exception optimizing " 207 + reader.getClassName(), ex); 208 } 209 } else { 210 MethodOptimizer moptimizer = (MethodOptimizer) optimizer; 211 for (MethodNode m : cn.methods) { 212 if ((m.access & Opcodes.ACC_ABSTRACT) == 0 213 && (m.access & Opcodes.ACC_NATIVE) == 0) { 214 numMethods += 1; 215 try { 216 boolean optim = moptimizer. 217 optimize(this::log, pools, modulePool, cn, 218 m, resolver); 219 if (optim) { 220 optimized = true; 221 } 222 } catch (Throwable ex) { 223 throw new PluginException("Exception optimizing " 224 + reader.getClassName() + "." + m.name, ex); 225 } 226 227 } 228 } 229 } 230 } 231 232 if (optimized) { 233 writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 234 try { 235 // add a validation layer in between to check for class vallidity 236 CheckClassAdapter ca = new CheckClassAdapter(writer); 237 cn.accept(ca); 238 } catch (Exception ex) { 239 throw new PluginException("Exception optimizing class " + cn.name, ex); 240 } 241 } 242 } 243 return writer; 244 } 245 246 @Override 247 public String getDescription() { 248 return PluginsResourceBundle.getDescription(NAME); 249 } 250 251 @Override 252 public boolean hasArguments() { 253 return true; 254 } 255 256 @Override 257 public String getArgumentsDescription() { 258 return PluginsResourceBundle.getArgument(NAME); 259 } 260 261 @Override 262 public void configure(Map<String, String> config) { 263 String strategies = config.get(NAME); 264 String[] arr = strategies.split(","); 265 for (String s : arr) { 266 if (s.equals(ALL)) { 267 optimizers.clear(); 268 optimizers.add(new ForNameFolding()); 269 break; 270 } else if (s.equals(FORNAME_REMOVAL)) { 271 optimizers.add(new ForNameFolding()); 272 } else { 273 throw new IllegalArgumentException("Unknown optimization: " + s); 274 } 275 } 276 String f = config.get(LOG); 277 if (f != null) { 278 try { 279 stream = new FileOutputStream(f); 280 } catch (IOException ex) { 281 throw new UncheckedIOException(ex); 282 } 283 } 284 } 285 }