1 /* 2 * Copyright (c) 2016, 2020, 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.BufferedReader; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.nio.file.Files; 33 import java.util.EnumSet; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Set; 37 38 import jdk.internal.access.JavaLangInvokeAccess; 39 import jdk.internal.access.SharedSecrets; 40 import jdk.tools.jlink.plugin.Plugin; 41 import jdk.tools.jlink.plugin.PluginException; 42 import jdk.tools.jlink.plugin.ResourcePool; 43 import jdk.tools.jlink.plugin.ResourcePoolBuilder; 44 import jdk.tools.jlink.plugin.ResourcePoolEntry; 45 46 /** 47 * Plugin to generate java.lang.invoke classes. 48 * 49 * The plugin reads in a file generated by running any application with 50 * {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true}. This is done 51 * automatically during build, see make/GenerateLinkOptData.gmk. See 52 * build/tools/classlist/HelloClasslist.java for the training application. 53 * 54 * HelloClasslist tries to reflect common use of java.lang.invoke during early 55 * startup and warmup in various applications. To ensure a good default 56 * trade-off between static footprint and startup the application should be 57 * relatively conservative. 58 * 59 * When using jlink to build a custom application runtime, generating a trace 60 * file using {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true} and 61 * feeding that into jlink using {@code --generate-jli-classes=@trace_file} can 62 * help improve startup time. 63 */ 64 public final class GenerateJLIClassesPlugin implements Plugin { 65 66 private static final String NAME = "generate-jli-classes"; 67 68 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME); 69 70 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt"; 71 72 private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); 73 74 String mainArgument; 75 String[] lines = null; 76 77 public GenerateJLIClassesPlugin() { 78 } 79 80 @Override 81 public String getName() { 82 return NAME; 83 } 84 85 @Override 86 public String getDescription() { 87 return DESCRIPTION; 88 } 89 90 @Override 91 public Set<State> getState() { 92 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); 93 } 94 95 @Override 96 public boolean hasArguments() { 97 return true; 98 } 99 100 @Override 101 public String getArgumentsDescription() { 102 return PluginsResourceBundle.getArgument(NAME); 103 } 104 105 @Override 106 public void configure(Map<String, String> config) { 107 mainArgument = config.get(NAME); 108 } 109 110 // Repeat def, we do not have access right to InvokerBytecodeGeneratorHelper 111 // Must be exact same! 112 private static final String DIRECT_METHOD_HOLDER_ENTRY = 113 "/java.base/java/lang/invoke/DirectMethodHandle$Holder.class"; 114 private static final String DELEGATING_METHOD_HOLDER_ENTRY = 115 "/java.base/java/lang/invoke/DelegatingMethodHandle$Holder.class"; 116 private static final String INVOKERS_HOLDER_ENTRY = 117 "/java.base/java/lang/invoke/Invokers$Holder.class"; 118 private static final String BASIC_FORMS_HOLDER_ENTRY = 119 "/java.base/java/lang/invoke/LambdaForm$Holder.class"; 120 121 @Override 122 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { 123 initialize(in); 124 // Copy all but DMH_ENTRY to out 125 in.transformAndCopy(entry -> { 126 // filter out placeholder entries 127 String path = entry.path(); 128 if (path.equals(DIRECT_METHOD_HOLDER_ENTRY) || 129 path.equals(DELEGATING_METHOD_HOLDER_ENTRY) || 130 path.equals(INVOKERS_HOLDER_ENTRY) || 131 path.equals(BASIC_FORMS_HOLDER_ENTRY)) { 132 return null; 133 } else { 134 return entry; 135 } 136 }, out); 137 138 // Generate LambdaForm Holder classes 139 generateHolderClasses(out); 140 // clear input. 141 lines = null; 142 return out.build(); 143 } 144 145 private void generateHolderClasses(ResourcePoolBuilder out) { 146 try { 147 Map<String, byte[]> result = JLIA.generateMethodHandleHolderClasses(lines); 148 if (result != null) { 149 result.forEach ((k,v) -> { 150 ResourcePoolEntry ndata = ResourcePoolEntry.create(k, v); 151 out.add(ndata); 152 }); 153 } 154 } catch (Exception ex) { 155 throw new PluginException(ex); 156 } 157 } 158 159 public void initialize(ResourcePool in) { 160 // Load configuration from the contents in the supplied input file 161 // - if none was supplied we look for the default file 162 if (mainArgument == null || !mainArgument.startsWith("@")) { 163 try (InputStream traceFile = 164 this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) { 165 if (traceFile != null) { 166 lines = new BufferedReader( 167 new InputStreamReader(traceFile)).lines().toArray(String[]::new); 168 } 169 } catch (Exception e) { 170 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e); 171 } 172 } else { 173 File file = new File(mainArgument.substring(1)); 174 try { 175 if (file.exists()) { 176 lines = Files.readAllLines(file.toPath()).toArray(new String[0]); 177 } 178 } catch (Exception e) { 179 throw new PluginException("Couldn't read " + file.getName(), e); 180 } 181 } 182 } 183 }