/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.internal;
import static com.github.anba.es6draft.runtime.internal.Errors.newInternalError;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import com.github.anba.es6draft.compiler.assembler.Handle;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.runtime.ExecutionContext;
/**
* Support class for native function calls.
*/
public final class NativeCalls {
private NativeCalls() {
}
/**
* Returns the native call name.
*
* @param name
* the native call function name
* @return the native call name
*/
public static String getNativeCallName(String name) {
return "native:" + name;
}
private static final Handle BOOTSTRAP;
static {
MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
BOOTSTRAP = MethodName.findStatic(NativeCalls.class, "bootstrapDynamic", mt).toHandle();
}
/**
* Returns the native call bootstrap handle.
*
* @return the bootstrap handle
*/
public static Handle getNativeCallBootstrap() {
return BOOTSTRAP;
}
// TODO: Make runtime context specific.
private static final CopyOnWriteArraySet<Class<?>> lookupClasses = new CopyOnWriteArraySet<>(
Collections.singletonList(RuntimeFunctions.class));
/**
* Adds {@code clazz} to the lookup classes.
*
* @param clazz
* the class object
*/
public static void register(Class<?> clazz) {
lookupClasses.add(Objects.requireNonNull(clazz));
}
/**
* Removes {@code clazz} from the lookup classes.
*
* @param clazz
* the class object
*/
public static void unregister(Class<?> clazz) {
lookupClasses.remove(Objects.requireNonNull(clazz));
}
private static final ConcurrentHashMap<String, MethodHandle> nativeMethods = new ConcurrentHashMap<>();
/**
* Returns the native call {@code CallSite} object.
*
* @param caller
* the caller lookup object
* @param name
* the native call name
* @param type
* the native call type
* @return the native call {@code CallSite} object
*/
public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
MethodHandle target;
try {
MethodHandle mh = nativeMethods.computeIfAbsent(name, NativeCalls::getNativeMethodHandle);
if (isSpreadCall(mh, type)) {
target = forSpreadCall(mh, type);
} else {
target = mh.asType(type);
}
if (target != mh) {
MethodHandle invalidArgumentsHandle = invalidCallArgumentsExceptionHandle(name, type);
target = MethodHandles.catchException(target, ClassCastException.class, invalidArgumentsHandle);
}
} catch (IllegalArgumentException e) {
target = invalidCallHandle(name, type);
} catch (WrongMethodTypeException e) {
target = invalidCallArgumentsHandle(name, type, e);
}
return new ConstantCallSite(target);
}
private static boolean isSpreadCall(MethodHandle mh, MethodType type) {
int pcount = type.parameterCount();
return pcount > 0 && type.parameterType(pcount - 1).equals(Object[].class);
}
private static MethodHandle forSpreadCall(MethodHandle mh, MethodType type) {
int expectedParameters = mh.type().parameterCount();
int actualParameters = type.parameterCount();
if (!mh.isVarargsCollector() || !mh.type().parameterType(expectedParameters - 1).equals(Object[].class)) {
throw new WrongMethodTypeException("Not Object[] var-args collector");
}
if (expectedParameters > actualParameters) {
throw new WrongMethodTypeException("Too few arguments");
}
if (expectedParameters < actualParameters) {
int fixedCount = actualParameters - expectedParameters;
int firstFixed = expectedParameters - 1;
List<Class<?>> fixed = type.parameterList().subList(firstFixed, firstFixed + fixedCount);
mh = MethodHandles.collectArguments(mh, firstFixed, combineArraysMH);
mh = MethodHandles.collectArguments(mh, firstFixed, toObjectArray(fixed));
}
return mh.asType(type);
}
private static MethodHandle toObjectArray(List<Class<?>> types) {
MethodHandle mh = MethodHandles.identity(Object[].class);
mh = mh.asCollector(Object[].class, types.size());
return mh.asType(MethodType.methodType(Object[].class, types));
}
private static MethodHandle throwableGetMessageMH, combineArraysMH, invalidNativeCallMH,
invalidNativeCallArgumentsMH;
static {
MethodLookup lookup = new MethodLookup(MethodHandles.lookup());
throwableGetMessageMH = lookup.findVirtual(Throwable.class, "getMessage", MethodType.methodType(String.class));
combineArraysMH = lookup.findStatic("combineArrays",
MethodType.methodType(Object[].class, Object[].class, Object[].class));
invalidNativeCallMH = lookup.findStatic("invalidNativeCall",
MethodType.methodType(Object.class, String.class, ExecutionContext.class));
invalidNativeCallArgumentsMH = lookup.findStatic("invalidNativeCallArguments",
MethodType.methodType(Object.class, String.class, ExecutionContext.class));
}
@SuppressWarnings("unused")
private static Object[] combineArrays(Object[] a, Object[] b) {
Object[] result = new Object[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
@SuppressWarnings("unused")
private static Object invalidNativeCall(String name, ExecutionContext cx) {
throw newInternalError(cx, Messages.Key.InternalError, "Invalid native call: " + name);
}
@SuppressWarnings("unused")
private static Object invalidNativeCallArguments(String reason, ExecutionContext cx) {
throw newInternalError(cx, Messages.Key.InternalError, "Invalid native call arguments: " + reason);
}
private static MethodHandle invalidCallHandle(String name, MethodType type) {
MethodHandle mh = MethodHandles.insertArguments(invalidNativeCallMH, 0, name);
return MethodHandles.dropArguments(mh, 1, type.dropParameterTypes(0, 1).parameterArray());
}
private static MethodHandle invalidCallArgumentsHandle(String name, MethodType type, WrongMethodTypeException e) {
// Add native call name?
MethodHandle mh = MethodHandles.insertArguments(invalidNativeCallArgumentsMH, 0, e.getMessage());
return MethodHandles.dropArguments(mh, 1, type.dropParameterTypes(0, 1).parameterArray());
}
private static MethodHandle invalidCallArgumentsExceptionHandle(String name, MethodType type) {
// Add native call name?
return MethodHandles.filterArguments(invalidNativeCallArgumentsMH, 0, throwableGetMessageMH);
}
private static MethodHandle getNativeMethodHandle(String name) {
if (!name.startsWith("native:")) {
throw new IllegalArgumentException();
}
String methodName = name.substring("native:".length());
for (Class<?> lookupClass : lookupClasses) {
MethodLookup lookup = new MethodLookup(MethodHandles.publicLookup().in(lookupClass));
Method m = findMethod(lookup, methodName);
if (m == null) {
continue;
}
MethodHandle mh;
try {
mh = lookup.getLookup().unreflect(m);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException();
}
// Allow to omit execution context argument.
MethodType type = mh.type();
if (type.parameterCount() == 0 || !type.parameterType(0).equals(ExecutionContext.class)) {
mh = MethodHandles.dropArguments(mh, 0, ExecutionContext.class);
}
// Allow void return type.
if (type.returnType() == void.class) {
mh = MethodHandles.filterReturnValue(mh, MethodHandles.constant(Object.class, UNDEFINED));
}
return mh;
}
throw new IllegalArgumentException();
}
private static Method findMethod(MethodLookup lookup, String name) {
for (Method m : lookup.getLookup().lookupClass().getDeclaredMethods()) {
int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && m.getName().equals(name)) {
return m;
}
}
return null;
}
}