Skip to content

Commit

Permalink
Support for Unsafe.allocateInstance in NI
Browse files Browse the repository at this point in the history
  • Loading branch information
vjovanov committed Mar 16, 2022
1 parent 9f923dd commit 99f6c8e
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@

import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.util.Arrays;

public interface ReflectionRegistry {
void register(ConfigurationCondition condition, Class<?>... classes);
default void register(ConfigurationCondition condition, Class<?>... classes) {
Arrays.stream(classes).forEach(clazz -> register(condition, false, clazz));
}

void register(ConfigurationCondition condition, boolean unsafeAllocated, Class<?> clazz);

void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods);

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
List<String> accessFilterFiles = new ArrayList<>();
boolean experimentalClassLoaderSupport = true;
boolean experimentalClassDefineSupport = false;
boolean experimentalUnsafeAllocationSupport = false;
boolean experimentalOmitClasspathConfig = false;
boolean build = false;
boolean configurationWithOrigins = false;
Expand Down Expand Up @@ -187,6 +188,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
experimentalClassDefineSupport = true;
} else if (token.startsWith("experimental-class-define-support=")) {
experimentalClassDefineSupport = Boolean.parseBoolean(getTokenValue(token));
} else if (token.equals("experimental-unsafe-allocation-support")) {
experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token));
} else if (token.startsWith("experimental-unsafe-allocation-support=")) {
experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token));
} else if (token.startsWith("config-write-period-secs=")) {
configWritePeriod = parseIntegerOrNegative(getTokenValue(token));
if (configWritePeriod <= 0) {
Expand Down Expand Up @@ -370,7 +375,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c

try {
BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier,
experimentalClassLoaderSupport, experimentalClassDefineSupport, trackReflectionMetadata);
experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata);
} catch (Throwable t) {
return error(3, t.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType
private boolean allPublicClasses;
private boolean allDeclaredFields;
private boolean allPublicFields;
private boolean unsafeAllocated;
private ConfigurationMemberAccessibility allDeclaredMethodsAccess = ConfigurationMemberAccessibility.NONE;
private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE;
private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE;
Expand Down Expand Up @@ -269,6 +270,7 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate<Boolean, Boo
allPublicClasses = flagPredicate.test(allPublicClasses, other.allPublicClasses);
allDeclaredFields = flagPredicate.test(allDeclaredFields, other.allDeclaredFields);
allPublicFields = flagPredicate.test(allPublicFields, other.allPublicFields);
unsafeAllocated = flagPredicate.test(unsafeAllocated, other.unsafeAllocated);
allDeclaredMethodsAccess = accessCombiner.apply(allDeclaredMethodsAccess, other.allDeclaredMethodsAccess);
allPublicMethodsAccess = accessCombiner.apply(allPublicMethodsAccess, other.allPublicMethodsAccess);
allDeclaredConstructorsAccess = accessCombiner.apply(allDeclaredConstructorsAccess, other.allDeclaredConstructorsAccess);
Expand Down Expand Up @@ -373,6 +375,10 @@ public synchronized void setAllPublicClasses() {
allPublicClasses = true;
}

public void setUnsafeAllocated() {
this.unsafeAllocated = true;
}

public synchronized void setAllDeclaredFields() {
allDeclaredFields = true;
removeFields(ConfigurationMemberDeclaration.DECLARED);
Expand Down Expand Up @@ -430,6 +436,8 @@ public synchronized void printJson(JsonWriter writer) throws IOException {
optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods");
optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredConstructors");
optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicConstructors");
optionallyPrintJsonBoolean(writer, unsafeAllocated, "unsafeAllocated");

if (fields != null) {
writer.append(',').newline().quote("fields").append(':');
JsonPrinter.printCollection(writer, fields.entrySet(), Map.Entry.comparingByKey(), ConfigurationType::printField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ public boolean registerAllConstructors(boolean queriedOnly, ConfigurationType ty
return true;
}

@Override
public void registerUnsafeAllocated(ConfigurationType type) {
type.setUnsafeAllocated();
}

@Override
public void registerMethod(boolean queriedOnly, ConfigurationType type, String methodName, List<ConfigurationType> methodParameterTypes) {
type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ public void processEntry(Map<String, ?> entry, ConfigurationSet configurationSet
resourceConfiguration.addBundle(condition, classNames, locales, baseName);
break;
}
case "allocateInstance": {
configuration.getOrCreateType(condition, clazz).setUnsafeAllocated();
break;
}
default:
System.err.println("Unsupported reflection method: " + function);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class ReflectionConfigurationParser<T> extends ConfigurationParser
private static final List<String> OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors",
"allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields",
"allDeclaredClasses", "allPermittedSubclasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY,
"queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods");
"queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated");

public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate<T> delegate) {
this(delegate, true);
Expand Down Expand Up @@ -165,6 +165,11 @@ private void parseClass(Map<String, Object> data) {
delegate.registerPublicMethods(true, clazz);
}
break;
case "unsafeAllocated":
if (asBoolean(value, "unsafeAllocated")) {
delegate.registerUnsafeAllocated(clazz);
}
break;
case "methods":
parseMethods(false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public interface ReflectionConfigurationParserDelegate<T> {

boolean registerAllConstructors(boolean queriedOnly, T type);

void registerUnsafeAllocated(T clazz);

String getTypeName(T type);

String getSimpleName(T type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import java.util.Map;

import com.oracle.svm.core.configure.ConfigurationFile;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.api.replacements.Snippet;
import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter;
Expand Down Expand Up @@ -69,7 +70,6 @@
import org.graalvm.compiler.replacements.SnippetTemplate.SnippetInfo;
import org.graalvm.compiler.word.BarrieredAccess;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.graalvm.word.LocationIdentity;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordFactory;
Expand Down Expand Up @@ -105,8 +105,6 @@ public abstract class SubstrateAllocationSnippets extends AllocationSnippets {
private static final SubstrateForeignCallDescriptor ARRAY_HUB_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "arrayHubErrorStub", true);
private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, HUB_ERROR, ARRAY_HUB_ERROR};

private static final String RUNTIME_REFLECTION_TYPE_NAME = RuntimeReflection.class.getTypeName();

public static void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) {
foreignCalls.register(FOREIGN_CALLS);
}
Expand Down Expand Up @@ -216,8 +214,8 @@ private static void hubErrorStub(DynamicHub hub) throws InstantiationException {
} else if (!LayoutEncoding.isInstance(hub.getLayoutEncoding())) {
throw new InstantiationException("Cannot allocate instance.");
} else if (!hub.isInstantiated()) {
throw new IllegalArgumentException("Class " + DynamicHub.toClass(hub).getTypeName() +
" is instantiated reflectively but was never registered. Register the class by using " + RUNTIME_REFLECTION_TYPE_NAME);
throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." +
" Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + ".");
} else {
throw VMError.shouldNotReachHere();
}
Expand Down Expand Up @@ -250,8 +248,8 @@ private static void arrayHubErrorStub(DynamicHub elementType) {
} else if (elementType == DynamicHub.fromClass(void.class)) {
throw new IllegalArgumentException("Cannot allocate void array.");
} else if (elementType.getArrayHub() == null || !elementType.getArrayHub().isInstantiated()) {
throw new IllegalArgumentException("Class " + DynamicHub.toClass(elementType).getTypeName() + "[]" +
" is instantiated reflectively but was never registered. Register the class by using " + RUNTIME_REFLECTION_TYPE_NAME);
throw new IllegalArgumentException("Class " + DynamicHub.toClass(elementType).getTypeName() + "[] is instantiated reflectively but was never registered." +
"Register the class by adding \"unsafeAllocated\" for the class in " + ConfigurationFile.REFLECTION.getFileName() + ".");
} else {
VMError.shouldNotReachHere();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement<C
return methods.length > 0;
}

@Override
public void registerUnsafeAllocated(ConditionalElement<Class<?>> clazz) {
registry.register(clazz.getCondition(), true, clazz.getElement());
}

@Override
public void registerMethod(boolean queriedOnly, ConditionalElement<Class<?>> type, String methodName, List<ConditionalElement<Class<?>>> methodParameterTypes) throws NoSuchMethodException {
Class<?>[] parameterTypesArray = getParameterTypes(methodParameterTypes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ public void afterRegistration(AfterRegistrationAccess arg) {
}

private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfigurationRegistry implements JNIRuntimeAccess.JNIRuntimeAccessibilitySupport, ReflectionRegistry {

@Override
public void register(ConfigurationCondition condition, Class<?>... classes) {
public void register(ConfigurationCondition condition, boolean unsafeAllocated, Class<?> clazz) {
assert !unsafeAllocated : "unsafeAllocated can be only set via Unsafe.allocateInstance, not via JNI.";
abortIfSealed();
registerConditionalConfiguration(condition, () -> newClasses.addAll(Arrays.asList(classes)));
registerConditionalConfiguration(condition, () -> newClasses.add(clazz));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl
private boolean sealed;

private final Set<Class<?>> reflectionClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<Class<?>> unsafeInstantiatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Map<Executable, ExecutableAccessibility> reflectionMethods = new ConcurrentHashMap<>();
private final Map<Executable, Object> methodAccessors = new ConcurrentHashMap<>();
private final Set<Field> reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>());
Expand All @@ -116,17 +117,16 @@ public ReflectionDataBuilder() {
}

@Override
public void register(ConfigurationCondition condition, Class<?>... classes) {
public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class<?> clazz) {
checkNotSealed();
registerConditionalConfiguration(condition, () -> registerClasses(classes));
}

private void registerClasses(Class<?>[] classes) {
for (Class<?> clazz : classes) {
registerConditionalConfiguration(condition, () -> {
if (unsafeInstantiated) {
unsafeInstantiatedClasses.add(clazz);
}
if (reflectionClasses.add(clazz)) {
modifiedClasses.add(clazz);
}
}
});
}

@Override
Expand Down Expand Up @@ -615,6 +615,9 @@ private void processClass(DuringAnalysisAccessImpl access, Class<?> clazz) {
* build the reflection metadata.
*/
type.registerAsReachable();
if (unsafeInstantiatedClasses.contains(clazz)) {
type.registerAsAllocated(null);
}

if (reflectionClasses.contains(clazz)) {
ClassForNameSupport.registerClass(clazz);
Expand Down
2 changes: 1 addition & 1 deletion vm/mx.vm/ni-ce
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DYNAMIC_IMPORTS=/substratevm
DISABLE_INSTALLABLES=true
EXCLUDE_COMPONENTS=pbm,pmh
NATIVE_IMAGES=native-image,lib:native-image-agent
NATIVE_IMAGES=native-image,lib:native-image-agent,lib:native-image-diagnostics-agent

0 comments on commit 99f6c8e

Please sign in to comment.