1 /* 2 * Copyright (c) 2016, 2019, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.serviceprovider; 26 27 import static java.lang.Thread.currentThread; 28 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.util.Arrays; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.ServiceConfigurationError; 35 import java.util.ServiceLoader; 36 import java.util.concurrent.atomic.AtomicLong; 37 import java.util.function.Supplier; 38 39 import org.graalvm.compiler.serviceprovider.SpeculationReasonGroup.SpeculationContextObject; 40 41 import jdk.vm.ci.code.BytecodePosition; 42 import jdk.vm.ci.meta.ResolvedJavaField; 43 import jdk.vm.ci.meta.ResolvedJavaMethod; 44 import jdk.vm.ci.meta.ResolvedJavaType; 45 import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; 46 import jdk.vm.ci.meta.SpeculationLog.SpeculationReasonEncoding; 47 import jdk.vm.ci.runtime.JVMCI; 48 import jdk.vm.ci.services.JVMCIPermission; 49 import jdk.vm.ci.services.Services; 50 51 /** 52 * Interface to functionality that abstracts over which JDK version Graal is running on. 53 */ 54 public final class GraalServices { 55 56 private GraalServices() { 57 } 58 59 /** 60 * Gets an {@link Iterable} of the providers available for a given service. 61 * 62 * @throws SecurityException if on JDK8 and a security manager is present and it denies 63 * {@link JVMCIPermission} 64 */ 65 public static <S> Iterable<S> load(Class<S> service) { 66 Iterable<S> iterable = ServiceLoader.load(service); 67 return new Iterable<>() { 68 @Override 69 public Iterator<S> iterator() { 70 Iterator<S> iterator = iterable.iterator(); 71 return new Iterator<>() { 72 @Override 73 public boolean hasNext() { 74 return iterator.hasNext(); 75 } 76 77 @Override 78 public S next() { 79 S provider = iterator.next(); 80 // Allow Graal extensions to access JVMCI 81 openJVMCITo(provider.getClass()); 82 return provider; 83 } 84 85 @Override 86 public void remove() { 87 iterator.remove(); 88 } 89 }; 90 } 91 }; 92 } 93 94 /** 95 * Opens all JVMCI packages to the module of a given class. This relies on JVMCI already having 96 * opened all its packages to the module defining {@link GraalServices}. 97 * 98 * @param other all JVMCI packages will be opened to the module defining this class 99 */ 100 static void openJVMCITo(Class<?> other) { 101 Module jvmciModule = JVMCI_MODULE; 102 Module otherModule = other.getModule(); 103 if (jvmciModule != otherModule) { 104 for (String pkg : jvmciModule.getPackages()) { 105 if (!jvmciModule.isOpen(pkg, otherModule)) { 106 // JVMCI initialization opens all JVMCI packages 107 // to Graal which is a prerequisite for Graal to 108 // open JVMCI packages to other modules. 109 JVMCI.getRuntime(); 110 111 jvmciModule.addOpens(pkg, otherModule); 112 } 113 } 114 } 115 } 116 117 /** 118 * Gets the provider for a given service for which at most one provider must be available. 119 * 120 * @param service the service whose provider is being requested 121 * @param required specifies if an {@link InternalError} should be thrown if no provider of 122 * {@code service} is available 123 * @return the requested provider if available else {@code null} 124 * @throws SecurityException if on JDK8 and a security manager is present and it denies 125 * {@link JVMCIPermission} 126 */ 127 public static <S> S loadSingle(Class<S> service, boolean required) { 128 assert !service.getName().startsWith("jdk.vm.ci") : "JVMCI services must be loaded via " + Services.class.getName(); 129 Iterable<S> providers = load(service); 130 S singleProvider = null; 131 try { 132 for (Iterator<S> it = providers.iterator(); it.hasNext();) { 133 singleProvider = it.next(); 134 if (it.hasNext()) { 135 S other = it.next(); 136 throw new InternalError(String.format("Multiple %s providers found: %s, %s", service.getName(), singleProvider.getClass().getName(), other.getClass().getName())); 137 } 138 } 139 } catch (ServiceConfigurationError e) { 140 // If the service is required we will bail out below. 141 } 142 if (singleProvider == null) { 143 if (required) { 144 throw new InternalError(String.format("No provider for %s found", service.getName())); 145 } 146 } 147 return singleProvider; 148 } 149 150 /** 151 * Gets the class file bytes for {@code c}. 152 */ 153 public static InputStream getClassfileAsStream(Class<?> c) throws IOException { 154 String classfilePath = c.getName().replace('.', '/') + ".class"; 155 return c.getModule().getResourceAsStream(classfilePath); 156 } 157 158 private static final Module JVMCI_MODULE = Services.class.getModule(); 159 160 /** 161 * A JVMCI package dynamically exported to trusted modules. 162 */ 163 private static final String JVMCI_RUNTIME_PACKAGE = "jdk.vm.ci.runtime"; 164 static { 165 assert JVMCI_MODULE.getPackages().contains(JVMCI_RUNTIME_PACKAGE); 166 } 167 168 /** 169 * Determines if invoking {@link Object#toString()} on an instance of {@code c} will only run 170 * trusted code. 171 */ 172 public static boolean isToStringTrusted(Class<?> c) { 173 Module module = c.getModule(); 174 Module jvmciModule = JVMCI_MODULE; 175 assert jvmciModule.getPackages().contains("jdk.vm.ci.runtime"); 176 if (module == jvmciModule || jvmciModule.isOpen(JVMCI_RUNTIME_PACKAGE, module)) { 177 // Can access non-statically-exported package in JVMCI 178 return true; 179 } 180 return false; 181 } 182 183 /** 184 * An implementation of {@link SpeculationReason} based on direct, unencoded values. 185 */ 186 static final class DirectSpeculationReason implements SpeculationReason { 187 final int groupId; 188 final String groupName; 189 final Object[] context; 190 private SpeculationReasonEncoding encoding; 191 192 DirectSpeculationReason(int groupId, String groupName, Object[] context) { 193 this.groupId = groupId; 194 this.groupName = groupName; 195 this.context = context; 196 } 197 198 @Override 199 public boolean equals(Object obj) { 200 if (obj instanceof DirectSpeculationReason) { 201 DirectSpeculationReason that = (DirectSpeculationReason) obj; 202 return this.groupId == that.groupId && Arrays.equals(this.context, that.context); 203 } 204 return false; 205 } 206 207 @Override 208 public int hashCode() { 209 return groupId + Arrays.hashCode(this.context); 210 } 211 212 @Override 213 public String toString() { 214 return String.format("%s@%d%s", groupName, groupId, Arrays.toString(context)); 215 } 216 217 @Override 218 public SpeculationReasonEncoding encode(Supplier<SpeculationReasonEncoding> encodingSupplier) { 219 if (encoding == null) { 220 encoding = encodingSupplier.get(); 221 encoding.addInt(groupId); 222 for (Object o : context) { 223 if (o == null) { 224 encoding.addInt(0); 225 } else { 226 addNonNullObject(encoding, o); 227 } 228 } 229 } 230 return encoding; 231 } 232 233 static void addNonNullObject(SpeculationReasonEncoding encoding, Object o) { 234 Class<? extends Object> c = o.getClass(); 235 if (c == String.class) { 236 encoding.addString((String) o); 237 } else if (c == Byte.class) { 238 encoding.addByte((Byte) o); 239 } else if (c == Short.class) { 240 encoding.addShort((Short) o); 241 } else if (c == Character.class) { 242 encoding.addShort((Character) o); 243 } else if (c == Integer.class) { 244 encoding.addInt((Integer) o); 245 } else if (c == Long.class) { 246 encoding.addLong((Long) o); 247 } else if (c == Float.class) { 248 encoding.addInt(Float.floatToRawIntBits((Float) o)); 249 } else if (c == Double.class) { 250 encoding.addLong(Double.doubleToRawLongBits((Double) o)); 251 } else if (o instanceof Enum) { 252 encoding.addInt(((Enum<?>) o).ordinal()); 253 } else if (o instanceof ResolvedJavaMethod) { 254 encoding.addMethod((ResolvedJavaMethod) o); 255 } else if (o instanceof ResolvedJavaType) { 256 encoding.addType((ResolvedJavaType) o); 257 } else if (o instanceof ResolvedJavaField) { 258 encoding.addField((ResolvedJavaField) o); 259 } else if (o instanceof SpeculationContextObject) { 260 SpeculationContextObject sco = (SpeculationContextObject) o; 261 // These are compiler objects which all have the same class 262 // loader so the class name uniquely identifies the class. 263 encoding.addString(o.getClass().getName()); 264 sco.accept(new EncodingAdapter(encoding)); 265 } else if (o.getClass() == BytecodePosition.class) { 266 BytecodePosition p = (BytecodePosition) o; 267 while (p != null) { 268 encoding.addInt(p.getBCI()); 269 encoding.addMethod(p.getMethod()); 270 p = p.getCaller(); 271 } 272 } else { 273 throw new IllegalArgumentException("Unsupported type for encoding: " + c.getName()); 274 } 275 } 276 } 277 278 static class EncodingAdapter implements SpeculationContextObject.Visitor { 279 private final SpeculationReasonEncoding encoding; 280 281 EncodingAdapter(SpeculationReasonEncoding encoding) { 282 this.encoding = encoding; 283 } 284 285 @Override 286 public void visitBoolean(boolean v) { 287 encoding.addByte(v ? 1 : 0); 288 } 289 290 @Override 291 public void visitByte(byte v) { 292 encoding.addByte(v); 293 } 294 295 @Override 296 public void visitChar(char v) { 297 encoding.addShort(v); 298 } 299 300 @Override 301 public void visitShort(short v) { 302 encoding.addInt(v); 303 } 304 305 @Override 306 public void visitInt(int v) { 307 encoding.addInt(v); 308 } 309 310 @Override 311 public void visitLong(long v) { 312 encoding.addLong(v); 313 } 314 315 @Override 316 public void visitFloat(float v) { 317 encoding.addInt(Float.floatToRawIntBits(v)); 318 } 319 320 @Override 321 public void visitDouble(double v) { 322 encoding.addLong(Double.doubleToRawLongBits(v)); 323 } 324 325 @Override 326 public void visitObject(Object v) { 327 if (v == null) { 328 encoding.addInt(0); 329 } else { 330 DirectSpeculationReason.addNonNullObject(encoding, v); 331 } 332 } 333 } 334 335 static SpeculationReason createSpeculationReason(int groupId, String groupName, Object... context) { 336 return new DirectSpeculationReason(groupId, groupName, context); 337 } 338 339 /** 340 * Gets a unique identifier for this execution such as a process ID or a 341 * {@linkplain #getGlobalTimeStamp() fixed timestamp}. 342 */ 343 public static String getExecutionID() { 344 return Long.toString(ProcessHandle.current().pid()); 345 } 346 347 private static final AtomicLong globalTimeStamp = new AtomicLong(); 348 349 /** 350 * Gets a time stamp for the current process. This method will always return the same value for 351 * the current VM execution. 352 */ 353 public static long getGlobalTimeStamp() { 354 if (globalTimeStamp.get() == 0) { 355 globalTimeStamp.compareAndSet(0, System.currentTimeMillis()); 356 } 357 return globalTimeStamp.get(); 358 } 359 360 /** 361 * Returns an approximation of the total amount of memory, in bytes, allocated in heap memory 362 * for the thread of the specified ID. The returned value is an approximation because some Java 363 * virtual machine implementations may use object allocation mechanisms that result in a delay 364 * between the time an object is allocated and the time its size is recorded. 365 * <p> 366 * If the thread of the specified ID is not alive or does not exist, this method returns 367 * {@code -1}. If thread memory allocation measurement is disabled, this method returns 368 * {@code -1}. A thread is alive if it has been started and has not yet died. 369 * <p> 370 * If thread memory allocation measurement is enabled after the thread has started, the Java 371 * virtual machine implementation may choose any time up to and including the time that the 372 * capability is enabled as the point where thread memory allocation measurement starts. 373 * 374 * @param id the thread ID of a thread 375 * @return an approximation of the total memory allocated, in bytes, in heap memory for a thread 376 * of the specified ID if the thread of the specified ID exists, the thread is alive, 377 * and thread memory allocation measurement is enabled; {@code -1} otherwise. 378 * 379 * @throws IllegalArgumentException if {@code id} {@code <=} {@code 0}. 380 * @throws UnsupportedOperationException if the Java virtual machine implementation does not 381 * {@linkplain #isThreadAllocatedMemorySupported() support} thread memory allocation 382 * measurement. 383 */ 384 public static long getThreadAllocatedBytes(long id) { 385 JMXService jmx = JMXService.instance; 386 if (jmx == null) { 387 throw new UnsupportedOperationException(); 388 } 389 return jmx.getThreadAllocatedBytes(id); 390 } 391 392 /** 393 * Convenience method for calling {@link #getThreadAllocatedBytes(long)} with the id of the 394 * current thread. 395 */ 396 public static long getCurrentThreadAllocatedBytes() { 397 return getThreadAllocatedBytes(currentThread().getId()); 398 } 399 400 /** 401 * Returns the total CPU time for the current thread in nanoseconds. The returned value is of 402 * nanoseconds precision but not necessarily nanoseconds accuracy. If the implementation 403 * distinguishes between user mode time and system mode time, the returned CPU time is the 404 * amount of time that the current thread has executed in user mode or system mode. 405 * 406 * @return the total CPU time for the current thread if CPU time measurement is enabled; 407 * {@code -1} otherwise. 408 * 409 * @throws UnsupportedOperationException if the Java virtual machine does not 410 * {@linkplain #isCurrentThreadCpuTimeSupported() support} CPU time measurement for 411 * the current thread 412 */ 413 public static long getCurrentThreadCpuTime() { 414 JMXService jmx = JMXService.instance; 415 if (jmx == null) { 416 throw new UnsupportedOperationException(); 417 } 418 return jmx.getCurrentThreadCpuTime(); 419 } 420 421 /** 422 * Determines if the Java virtual machine implementation supports thread memory allocation 423 * measurement. 424 */ 425 public static boolean isThreadAllocatedMemorySupported() { 426 JMXService jmx = JMXService.instance; 427 if (jmx == null) { 428 return false; 429 } 430 return jmx.isThreadAllocatedMemorySupported(); 431 } 432 433 /** 434 * Determines if the Java virtual machine supports CPU time measurement for the current thread. 435 */ 436 public static boolean isCurrentThreadCpuTimeSupported() { 437 JMXService jmx = JMXService.instance; 438 if (jmx == null) { 439 return false; 440 } 441 return jmx.isCurrentThreadCpuTimeSupported(); 442 } 443 444 /** 445 * Gets the input arguments passed to the Java virtual machine which does not include the 446 * arguments to the {@code main} method. This method returns an empty list if there is no input 447 * argument to the Java virtual machine. 448 * <p> 449 * Some Java virtual machine implementations may take input arguments from multiple different 450 * sources: for examples, arguments passed from the application that launches the Java virtual 451 * machine such as the 'java' command, environment variables, configuration files, etc. 452 * <p> 453 * Typically, not all command-line options to the 'java' command are passed to the Java virtual 454 * machine. Thus, the returned input arguments may not include all command-line options. 455 * 456 * @return the input arguments to the JVM or {@code null} if they are unavailable 457 */ 458 public static List<String> getInputArguments() { 459 JMXService jmx = JMXService.instance; 460 if (jmx == null) { 461 return null; 462 } 463 return jmx.getInputArguments(); 464 } 465 }