Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions compiler/docs/ReplayCompilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,53 @@ The `-Djdk.graal.ReplayDivergenceIsFailure=true` argument prevents using default
```shell
mx replaycomp -Djdk.graal.ReplayDivergenceIsFailure=true ./replay-files
```

## Performance Counters

When replaying with the `--benchmark` command on the AMD64 Linux platform, the replay launcher can count hardware
performance events via the [PAPI](https://github.com/icl-utk-edu/papi) library. To enable this, it is necessary to set
up PAPI and build the optional PAPI bridge library. Note that recent architectures may not be supported; see the list
here: https://github.com/icl-utk-edu/papi/wiki/Supported-Architectures.

### PAPI Setup

PAPI can usually be installed with the package manager (`papi-devel` on Fedora, `libpapi-dev` on Ubuntu). The PAPI
bridge links against the PAPI available on the system.

To monitor hardware events, it may be necessary to lower the system's restrictions for accessing hardware performance
counters like shown below.

```shell
sudo sysctl kernel.perf_event_paranoid=-1
```

Additionally, the performance monitoring library (libpfm) may fail to select the appropriate performance monitoring unit
(PMU). The selection can be forced to `amd64` using an environment variable.

```shell
export LIBPFM_FORCE_PMU=amd64
```

To discover the available counters, use the `papi_avail` and `papi_native_avail` commands, which are part of the PAPI
installation. Verify that a particular event like `PAPI_TOT_INS` (retired instruction count) is counted using the
`papi_command_line` utility.

```shell
papi_avail
papi_native_avail
papi_command_line PAPI_TOT_INS
```

### PAPI Bridge

The below command builds the PAPI bridge library using the PAPI library available on the system.

```shell
ENABLE_PAPI_BRIDGE=true mx build --dependencies PAPI_BRIDGE
```

The launcher accepts a comma-separated list of event names. The event counts are reported for every benchmark iteration.

```shell
ENABLE_PAPI_BRIDGE=true mx replaycomp ./replay-files --benchmark --event-names PAPI_TOT_INS
```
74 changes: 73 additions & 1 deletion compiler/mx.compiler/mx_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from __future__ import print_function
import os
from functools import total_ordering
from functools import total_ordering, lru_cache
from os.path import join, exists, basename, dirname, isdir
import argparse
from argparse import ArgumentParser, RawDescriptionHelpFormatter, REMAINDER
Expand All @@ -43,6 +43,7 @@
from mx_sdk_benchmark import JVMCI_JDK_TAG, DaCapoBenchmarkSuite, ScalaDaCapoBenchmarkSuite, RenaissanceBenchmarkSuite
import mx_graal_benchmark #pylint: disable=unused-import

from mx_cmake import CMakeNinjaProject
import mx_gate
from mx_gate import Task

Expand Down Expand Up @@ -1564,6 +1565,7 @@ def mx_register_dynamic_suite_constituents(register_project, register_distributi
graal_jdk_dist.description = "GraalJDK CE distribution"
graal_jdk_dist.maven = {'groupId': 'org.graalvm', 'tag': 'graaljdk'}
register_distribution(graal_jdk_dist)
register_papi_bridge_dynamic_suite_constituents(register_project, register_distribution)


def _parse_graaljdk_edition(description, args):
Expand All @@ -1585,18 +1587,88 @@ def profdiff(args):
vm_args = ['-cp', cp, 'org.graalvm.profdiff.Profdiff'] + args
return jdk.run_java(args=vm_args)

ENABLE_PAPI_BRIDGE_VAR = 'ENABLE_PAPI_BRIDGE'
"""The environment variable that enables the PAPI bridge library."""

def papi_smoke_test():
"""Verifies that PAPI is available and can measure the PAPI_TOT_INS event."""
command = ['papi_command_line', 'PAPI_TOT_INS']
capture = mx.OutputCapture()
try:
retval = mx.run(command, nonZeroIsFatal=False, out=capture)
except FileNotFoundError:
mx.abort(f'The PAPI utility `{command[0]}` could not be found in your PATH or is not executable. '
f'Please ensure PAPI is installed and configured correctly, or unset the {ENABLE_PAPI_BRIDGE_VAR} environment variable.')
output = repr(capture)
if retval != 0 or 'Successfully added' not in output:
mx.abort(f'''The PAPI smoke test failed. Your system may not be supported by PAPI, or it may require some extra
configuration. Note that PAPI may not support recent architectures.

You can try:
- Widening permissions for performance events, e.g., sudo sysctl kernel.perf_event_paranoid=-1
- Forcing a specific PMU model, e.g., export LIBPFM_FORCE_PMU=amd64

Test command: {" ".join(command)}

Test output:
{output}''')

@lru_cache
def papi_bridge_enabled() -> bool:
"""Checks if the PAPI bridge library is enabled with an environment variable and runs a smoke test."""
if mx.get_env(ENABLE_PAPI_BRIDGE_VAR) == 'true':
if mx.get_os() != 'linux' or mx.get_arch() != 'amd64':
mx.abort(f'The PAPI bridge library is not supported on this platform. Please unset the {ENABLE_PAPI_BRIDGE_VAR} environment variable.')
papi_smoke_test()
return True
else:
return False

PAPI_BRIDGE_DISTRIBUTION = 'PAPI_BRIDGE'
"""The name of the distribution of the PAPI bridge library."""

def register_papi_bridge_dynamic_suite_constituents(register_project, register_distribution):
"""Registers the PAPI bridge library as a dynamic suite constituent if it is enabled and supported."""
if papi_bridge_enabled():
package = 'org.graalvm.papibridge'
register_project(CMakeNinjaProject(
suite=_suite,
name=package,
deps=[],
buildDependencies=['sdk:LLVM_TOOLCHAIN'],
workingSets=None,
subDir='src',
ninja_targets=['<lib:papibridge>'],
results=['<lib:papibridge>'],
vpath=True,
cmakeConfig={'CMAKE_C_COMPILER': '<path:LLVM_TOOLCHAIN>/bin/<exe:clang>'},
))
register_distribution(mx.LayoutTARDistribution(_suite,
PAPI_BRIDGE_DISTRIBUTION,
[package],
{'./': f'dependency:{package}'},
None,
True,
None))


def replaycomp_vm_args(distributions):
"""Returns the VM arguments required to run the replay compilation launcher.

:param distributions the distributions to add to the classpath
:return the list of VM arguments
"""
extra_options = []
if papi_bridge_enabled():
path = os.path.join(mx.dependency(PAPI_BRIDGE_DISTRIBUTION).get_output(), 'libpapibridge.so')
extra_options.append(f'-Ddebug.jdk.graal.PAPIBridgePath={path}')
return [
'-XX:-UseJVMCICompiler',
'--enable-native-access=ALL-UNNAMED',
'--illegal-native-access=allow',
'--add-exports=java.base/jdk.internal.module=ALL-UNNAMED',
'-Djdk.graal.CompilationFailureAction=Print',
*extra_options,
'-cp',
mx.classpath(distributions, jdk=jdk),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@
import java.util.Map;

import org.graalvm.collections.EconomicMap;
import org.graalvm.jniutils.JNI;
import org.graalvm.jniutils.JNI.JNIEnv;
import org.graalvm.jniutils.JNICalls;
import org.graalvm.jniutils.JNIExceptionWrapper;
import org.graalvm.jniutils.JNIMethodScope;
import org.graalvm.jniutils.JNIUtil;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateThreadContext;
import org.graalvm.nativeimage.c.type.CTypeConversion;
Expand All @@ -52,6 +56,7 @@
import jdk.graal.compiler.hotspot.HotSpotGraalRuntime;
import jdk.graal.compiler.hotspot.HotSpotGraalServices;
import jdk.graal.compiler.hotspot.ProfileReplaySupport;
import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters;
import jdk.graal.compiler.hotspot.replaycomp.ReplayCompilationRunner;
import jdk.graal.compiler.options.OptionDescriptors;
import jdk.graal.compiler.options.OptionKey;
Expand Down Expand Up @@ -312,12 +317,117 @@ private static int replayCompilation(JNIEnv jniEnv,
} else {
args = argString.split("\n");
}
return ReplayCompilationRunner.run(args, TTY.out().out()).getStatus();
return ReplayCompilationRunner.run(args, TTY.out().out(), new LibgraalPAPIBridge(jniEnv)).getStatus();
} catch (Throwable t) {
JNIExceptionWrapper.throwInHotSpot(jniEnv, t);
return ReplayCompilationRunner.ExitStatus.Failure.getStatus();
} finally {
LibGraalSupportImpl.doReferenceHandling();
}
}

/**
* The implementation that allows libgraal to interact with the PAPI bridge library,
* piggybacking HotSpot's {@link System#load} implementation.
* <p>
* The methods can be called from attached threads which provide their {@link JNIEnv} via
* {@link JNIMethodScope}.
*
* @see HardwarePerformanceCounters.PAPIBridge
*/
@SuppressWarnings("try")
private static final class LibgraalPAPIBridge implements HardwarePerformanceCounters.PAPIBridge {
private final JNI.JClass hpcClass;

private final JNI.JClass stringClass;

private final JNICalls.JNIMethod linkAndInitializeOnceMethod;

private final JNICalls.JNIMethod createEventSetMethod;

private final JNICalls.JNIMethod getNullMethod;

private final JNICalls.JNIMethod cleanAndDestroyEventSetMethod;

private final JNICalls.JNIMethod startMethod;

private final JNICalls.JNIMethod stopMethod;

LibgraalPAPIBridge(JNIEnv env) {
this.hpcClass = JNIUtil.findClass(env, Word.nullPointer(), JNIUtil.getBinaryName(HardwarePerformanceCounters.class.getName()), true);
this.stringClass = JNIUtil.findClass(env, Word.nullPointer(), JNIUtil.getBinaryName(String.class.getName()), true);
this.linkAndInitializeOnceMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "linkAndInitializeOnce", "()Z");
this.createEventSetMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "createEventSet", "([Ljava/lang/String;)I");
this.getNullMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "getNull", "()I");
this.cleanAndDestroyEventSetMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "cleanAndDestroyEventSet", "(I)Z");
this.startMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "start", "(I)Z");
this.stopMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "stop", "(I)[J");
}

@Override
public boolean linkAndInitializeOnce() {
JNI.JNIEnv env = JNIMethodScope.env();
return JNICalls.getDefault().callStaticBoolean(env, hpcClass, linkAndInitializeOnceMethod, StackValue.get(0));
}

@Override
public int createEventSet(String[] eventNames) {
JNI.JNIEnv env = JNIMethodScope.env();
JNI.JObjectArray eventNamesArray = JNIUtil.NewObjectArray(env, eventNames.length, stringClass, Word.nullPointer());
try {
copyStringArray(env, eventNames, eventNamesArray);
JNI.JValue args = StackValue.get(1, JNI.JValue.class);
args.addressOf(0).setJObject(eventNamesArray);
return JNICalls.getDefault().callStaticInt(env, hpcClass, createEventSetMethod, args);
} finally {
JNIUtil.DeleteLocalRef(env, eventNamesArray);
}
}

private static void copyStringArray(JNIEnv env, String[] source, JNI.JObjectArray dest) {
for (int i = 0; i < source.length; i++) {
JNI.JString eventName = JNIUtil.createHSString(env, source[i]);
try {
JNIUtil.SetObjectArrayElement(env, dest, i, eventName);
} finally {
JNIUtil.DeleteLocalRef(env, eventName);
}
}
}

@Override
public int getNull() {
JNI.JNIEnv env = JNIMethodScope.env();
return JNICalls.getDefault().callStaticInt(env, hpcClass, getNullMethod, StackValue.get(0));
}

@Override
public boolean cleanAndDestroyEventSet(int eventset) {
JNI.JNIEnv env = JNIMethodScope.env();
JNI.JValue args = StackValue.get(1, JNI.JValue.class);
args.addressOf(0).setInt(eventset);
return JNICalls.getDefault().callStaticBoolean(env, hpcClass, cleanAndDestroyEventSetMethod, args);
}

@Override
public boolean start(int eventset) {
JNI.JNIEnv env = JNIMethodScope.env();
JNI.JValue args = StackValue.get(1, JNI.JValue.class);
args.addressOf(0).setInt(eventset);
return JNICalls.getDefault().callStaticBoolean(env, hpcClass, startMethod, args);
}

@Override
public long[] stop(int eventset) {
JNI.JNIEnv env = JNIMethodScope.env();
JNI.JValue args = StackValue.get(1, JNI.JValue.class);
args.addressOf(0).setInt(eventset);
JNI.JLongArray result = JNICalls.getDefault().callStaticJObject(env, hpcClass, stopMethod, args);
try {
return JNIUtil.createArray(env, result);
} finally {
JNIUtil.DeleteLocalRef(env, result);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.oracle.truffle.runtime.hotspot.libgraal.LibGraalScope;

import jdk.graal.compiler.api.test.ModuleSupport;
import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters;
import jdk.graal.compiler.hotspot.replaycomp.ReplayCompilationRunner;
import jdk.graal.compiler.hotspot.test.LibGraalCompilationDriver;

Expand All @@ -49,7 +50,8 @@ public class ReplayCompilationLauncher {
/**
* Calls libgraal C entry point
* {@code jdk.graal.compiler.libgraal.LibGraalEntryPoints#replayCompilation}, which in turn
* calls {@link ReplayCompilationRunner#run(String[], PrintStream)}.
* calls
* {@link ReplayCompilationRunner#run(String[], PrintStream, HardwarePerformanceCounters.PAPIBridge)}.
*/
public static native int runInLibgraal(long isolateThread, long argBuffer);

Expand All @@ -71,7 +73,7 @@ public static void main(String[] args) {
}
} else {
System.out.println("Running in jargraal");
ReplayCompilationRunner.run(args, System.out).exitVM();
ReplayCompilationRunner.run(args, System.out, new HardwarePerformanceCounters.JargraalPAPIBridge()).exitVM();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.EconomicSet;
import org.junit.AfterClass;
Expand Down Expand Up @@ -168,7 +169,7 @@ public void recordAndExecuteReplayRunner() throws Throwable {
new String[]{"--compare-graphs=false", temp.path.toString(), "--benchmark", "--iterations=1"}
};
for (String[] arguments : argumentLists) {
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(arguments, TTY.out().out());
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(arguments, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge());
assertTrue(status == ReplayCompilationRunner.ExitStatus.Success);
}
});
Expand All @@ -182,15 +183,15 @@ public void unparsableReplayFileSucceeds() throws Throwable {
* this is not an error.
*/
assertTrue(Path.of(temp.path.toString(), "empty.json").toFile().createNewFile());
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out());
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge());
assertTrue(status == ReplayCompilationRunner.ExitStatus.Success);
});
}

@Test
public void emptyLauncherInputFails() throws Throwable {
runTest((temp) -> {
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out());
ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge());
assertTrue(status == ReplayCompilationRunner.ExitStatus.Failure);
});
}
Expand Down
Loading