Skip to content

Commit

Permalink
[GR-52320] Allow custom type resolving in profile replay
Browse files Browse the repository at this point in the history
PullRequest: graal/17089
  • Loading branch information
Felix Berlakovich committed Mar 15, 2024
2 parents d924b37 + 17f5cf2 commit 2d2bfe5
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jdk.graal.compiler.debug.test;

import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;

import jdk.graal.compiler.debug.StandardPathUtilitiesProvider;

public class StandardPathUtilitiesProviderTest {

@Test
public void sanitizesPathName() {
StandardPathUtilitiesProvider sut = new StandardPathUtilitiesProvider();
String sanitized = sut.sanitizeFileName("\0null");
Assert.assertEquals("_null", sanitized);

sanitized = sut.sanitizeFileName("path/with/slashes");
Assert.assertEquals("path_with_slashes", sanitized);
}

@Test
public void sanitizesPathNameOnWindows() {
Assume.assumeTrue(System.getProperty("os.name", "").startsWith("Windows"));

// Windows is pickier regarding path names
StandardPathUtilitiesProvider sut = new StandardPathUtilitiesProvider();
String sanitized = sut.sanitizeFileName("<lessthan>greaterthan:colon|bar*asterisk?question");
Assert.assertEquals("_lessthan_greaterthan_colon_bar_asterisk_question", sanitized);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,18 @@ public String sanitizeFileName(String name) {
}
buf.append('_');
}
return buf.toString();

/*
* On Windows, the original path might contain "/" as well (both "\" and "/" are equally
* valid path separators on Windows). Since File.separatorChar only reports "\" on Windows,
* we might have missed it during the previous sanitization. Paths.get should work now
* because we have removed all illegal characters and on Windows it canonicalizes the path
* to contain only "\". We thus replace any "/" that were converted to "\" here.
*/
String pathString = buf.toString();
String sanitizedPathString = Paths.get(pathString).toString();
sanitizedPathString = sanitizedPathString.replace(File.separatorChar, '_');
return sanitizedPathString;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ public Boolean getExpectedResult() {
return expectedResult;
}

/**
* Start the profile record/replay process. If {@link Options#SaveProfiles} is set, this method
* installs {@link StableProfileProvider}s to record profiling information that can be
* subsequently saved with {@link #profileReplayEpilogue}. If {@link Options#LoadProfiles} is
* set, the method initializes {@link StableProfileProvider}s with profiles loaded from disk.
* <p>
* Note that there might be cases where profiles cannot be restored, even if they are present in
* the on-disk representation. For example, the profiles of {@code invokedynamic} call sites
* (e.g. lambda expressions) cannot be restored due to unpredictable type names of the lambda
* objects. Depending on {@link Options#StrictProfiles}, the {@link StableProfileProvider}s will
* either throw an exception or emit a warning in this case.
* </p>
*/
public static ProfileReplaySupport profileReplayPrologue(DebugContext debug, Providers providers, int entryBCI, ResolvedJavaMethod method,
StableProfileProvider profileProvider, TypeFilter profileSaveFilter) {
if (SaveProfiles.getValue(debug.getOptions()) || LoadProfiles.getValue(debug.getOptions()) != null) {
Expand Down Expand Up @@ -199,6 +212,13 @@ public String formatLamdaName(ResolvedJavaMethod m) {
return null;
}

/**
* Finishes a previously started profile record/replay (see {@link #profileReplayPrologue}.
* Both, for record and replay, the method validates various expectations (see
* {@link Options#WarnAboutCodeSignatureMismatch} and
* {@link Options#WarnAboutGraphSignatureMismatch}). If {@link Options#SaveProfiles} is set, the
* method additionally saves the previously collected profiles to the given profile path.
*/
public void profileReplayEpilogue(DebugContext debug, CompilationResult result, StructuredGraph graph, StableProfileProvider profileProvider, CompilationIdentifier compilationId,
int entryBCI, ResolvedJavaMethod method) {
if ((SaveProfiles.getValue(debug.getOptions()) || LoadProfiles.getValue(debug.getOptions()) != null) && profileFilter.matches(method)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@

import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.MapCursor;

import jdk.graal.compiler.debug.GraalError;
import jdk.graal.compiler.debug.TTY;
import jdk.graal.compiler.nodes.StructuredGraph;

import jdk.vm.ci.meta.DeoptimizationReason;
import jdk.vm.ci.meta.JavaMethodProfile;
import jdk.vm.ci.meta.JavaTypeProfile;
Expand All @@ -48,6 +48,95 @@
* same answer for the entire compilation. This can improve the consistency of compilation results.
*/
public class StableProfileProvider implements ProfileProvider {

private final TypeResolver resolver;

/**
* The profile provider uses a type resolver during profile replay to resolve the string type
* names in type profiles to {@link ResolvedJavaType}s.
*/
public interface TypeResolver {
/**
* Try to resolve the given {@code typeName} to a {@link ResolvedJavaType}.
*
* @param method the method containing the type profile in which the type name occurs
* @param realProfile the currently loaded profile of {@code method}
* @param bci the bci at which the type occurred
* @param loadingIssuingType the type that triggered the load of the respective profile
* @param typeName the type name to be resolved
* @return a {@link ResolvedJavaType} if the type can be resolved, null otherwise
*/
ResolvedJavaType resolve(ResolvedJavaMethod method, ProfilingInfo realProfile, int bci, ResolvedJavaType loadingIssuingType, String typeName);
}

/**
* Default strategy for resolving type names into {@link ResolvedJavaType}s. This resolver
* follows a four-step process to resolve types:
* <ol>
* <li>If the type exists in the real profile, use the type from the real profile</li>
* <li>Try to resolve the type with the method owner as context</li>
* <li>Try to resolve the type with the load triggering type as context</li>
* <li>Try to resolve the type with any type occurring in the real profile as context</li>
* </ol>
*/
public static class DefaultTypeResolver implements TypeResolver {
@Override
public ResolvedJavaType resolve(ResolvedJavaMethod method, ProfilingInfo realProfile, int bci, ResolvedJavaType loadingIssuingType, String typeName) {
JavaTypeProfile actualProfile = realProfile.getTypeProfile(bci);

ResolvedJavaType actualType = null;
if (actualProfile != null) {
for (JavaTypeProfile.ProfiledType actual : actualProfile.getTypes()) {
if (actual.getType().getName().equals(typeName)) {
actualType = actual.getType();
break;
}
}
}

if (actualType == null) {
try {
actualType = UnresolvedJavaType.create(typeName).resolve(method.getDeclaringClass());
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
}

if (actualType == null) {
// try using the original type issuing the load operation of this profile
try {
actualType = UnresolvedJavaType.create(typeName).resolve(loadingIssuingType);
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
}
if (actualType == null) {
if (actualProfile != null) {
for (JavaTypeProfile.ProfiledType actual : actualProfile.getTypes()) {
try {
actualType = UnresolvedJavaType.create(typeName).resolve(actual.getType());
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
if (actualType != null) {
break;
}
}
}
}

return actualType;
}
}

public StableProfileProvider(TypeResolver resolver) {
this.resolver = resolver;
}

public StableProfileProvider() {
this.resolver = new DefaultTypeResolver();
}

private static final JavaTypeProfile NULL_PROFILE = new JavaTypeProfile(TriState.UNKNOWN, 1.0, new JavaTypeProfile.ProfiledType[0]);

private static final double[] NULL_SWITCH_PROBABILITIES = new double[0];
Expand Down Expand Up @@ -135,7 +224,7 @@ public ProfilingInfo getProfilingInfo(ResolvedJavaMethod method, boolean include
}

/**
* Lazy cache of the per method profile queries.
* Lazy cache of the per-method profile queries.
*/
public class CachingProfilingInfo implements ProfilingInfo {

Expand Down Expand Up @@ -184,62 +273,31 @@ void materializeProfile() {
TriState theNullSeen = parseTriState((String) symbolicTypeProfile.get("nullSeen"));
double notRecordedProbabilty = (double) symbolicTypeProfile.get("notRecordedProbability");
List<?> types = (List<?>) symbolicTypeProfile.get("types");
JavaTypeProfile.ProfiledType[] pitems = new JavaTypeProfile.ProfiledType[types.size()];
int i = 0;
ArrayList<JavaTypeProfile.ProfiledType> recoverableTypes = new ArrayList<>();
for (Object e : types) {
EconomicMap<String, Object> entry = (EconomicMap<String, Object>) e;
String typeName = (String) entry.get("type");
ResolvedJavaType actualType = null;

double probability = (double) entry.get("probability");
JavaTypeProfile actualProfile = realProfile.getTypeProfile(bci);
if (actualProfile != null) {
for (JavaTypeProfile.ProfiledType actual : actualProfile.getTypes()) {
if (actual.getType().getName().equals(typeName)) {
actualType = actual.getType();
break;
}
}
}
if (actualType == null) {
try {
actualType = UnresolvedJavaType.create(typeName).resolve(method.getDeclaringClass());
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
}
if (actualType == null) {
// try using the original type issuing the load operation of this profile
try {
actualType = UnresolvedJavaType.create(typeName).resolve(loadingIssuingType);
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
}
if (actualType == null) {
if (actualProfile != null) {
for (JavaTypeProfile.ProfiledType actual : actualProfile.getTypes()) {
try {
actualType = UnresolvedJavaType.create(typeName).resolve(actual.getType());
} catch (NoClassDefFoundError ncdfe) {
// do nothing
}
if (actualType != null) {
break;
}
}
}
}
if (actualType == null) {

ResolvedJavaType actualType = null;
actualType = resolver.resolve(method, realProfile, bci, loadingIssuingType, typeName);

if (actualType == null) {
if (frozen) {
throw new InternalError("Unable to load " + typeName + " for profile");
} else {
TTY.println("Unable to load " + typeName + " for profile");
}
} else {
/*
* Only create a JavaTypeProfile if we could resolve the type. The
* ProfiledType constructor will fail otherwise.
*/
recoverableTypes.add(new JavaTypeProfile.ProfiledType(actualType, probability));
}
pitems[i++] = new JavaTypeProfile.ProfiledType(actualType, probability);
}
typeProfile = new JavaTypeProfile(theNullSeen, notRecordedProbabilty, pitems);
typeProfile = new JavaTypeProfile(theNullSeen, notRecordedProbabilty, recoverableTypes.toArray(new JavaTypeProfile.ProfiledType[0]));
}
}

Expand Down

0 comments on commit 2d2bfe5

Please sign in to comment.