001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.runtime;
012
013import gololang.FunctionReference;
014
015import java.lang.invoke.*;
016import java.lang.invoke.MethodHandles.Lookup;
017import java.util.Arrays;
018
019import static java.lang.invoke.MethodHandles.guardWithTest;
020import static java.lang.invoke.MethodType.methodType;
021
022public final class ClosureCallSupport {
023
024  private ClosureCallSupport() {
025    throw new UnsupportedOperationException("Don't instantiate invokedynamic bootstrap class");
026  }
027
028  static class InlineCache extends MutableCallSite {
029
030    MethodHandle fallback;
031    final boolean constant;
032    final String[] argumentNames;
033
034    InlineCache(MethodType type, boolean constant, String[] argumentNames) {
035      super(type);
036      this.constant = constant;
037      this.argumentNames = argumentNames;
038    }
039  }
040
041  private static final MethodHandle GUARD;
042  private static final MethodHandle FALLBACK;
043
044  static {
045    try {
046      Lookup lookup = MethodHandles.lookup();
047
048      GUARD = lookup.findStatic(
049          ClosureCallSupport.class,
050          "guard",
051          methodType(boolean.class, FunctionReference.class, FunctionReference.class));
052
053      FALLBACK = lookup.findStatic(
054          ClosureCallSupport.class,
055          "fallback",
056          methodType(Object.class, InlineCache.class, Object[].class));
057    } catch (NoSuchMethodException | IllegalAccessException e) {
058      throw new Error("Could not bootstrap the required method handles", e);
059    }
060  }
061
062  public static CallSite bootstrap(Lookup caller, String name, MethodType type, Object... bsmArgs) {
063    boolean constant = ((int) bsmArgs[0]) == 1;
064    String[] argumentNames = new String[bsmArgs.length - 1];
065    for (int i = 0; i < bsmArgs.length - 1; i++) {
066      argumentNames[i] = (String) bsmArgs[i + 1];
067    }
068    InlineCache callSite = new InlineCache(type, constant, argumentNames);
069    MethodHandle fallbackHandle = FALLBACK
070        .bindTo(callSite)
071        .asCollector(Object[].class, type.parameterCount())
072        .asType(type);
073    callSite.fallback = fallbackHandle;
074    callSite.setTarget(fallbackHandle);
075    return callSite;
076  }
077
078  public static boolean guard(FunctionReference expected, FunctionReference actual) {
079    return expected == actual;
080  }
081
082  public static Object fallback(InlineCache callSite, Object[] args) throws Throwable {
083    FunctionReference targetFunctionReference = (FunctionReference) args[0];
084    MethodHandle target = targetFunctionReference.handle();
085    MethodHandle invoker = MethodHandles.dropArguments(target, 0, FunctionReference.class);
086    MethodType type = invoker.type();
087    if (callSite.argumentNames.length > 0) {
088      invoker = reorderArguments(
089          targetFunctionReference.parameterNames(),
090          invoker,
091          callSite.argumentNames);
092    }
093    if (target.isVarargsCollector()) {
094      if (TypeMatching.isLastArgumentAnArray(type.parameterCount(), args)) {
095        invoker = invoker.asFixedArity().asType(callSite.type());
096      } else {
097        invoker = invoker.asCollector(
098            Object[].class,
099            callSite.type().parameterCount() - target.type().parameterCount())
100          .asType(callSite.type());
101      }
102    } else {
103      invoker = invoker.asType(callSite.type());
104    }
105    if (callSite.constant) {
106      Object constantValue = invoker.invokeWithArguments(args);
107      MethodHandle constant;
108      if (constantValue == null) {
109        constant = MethodHandles.constant(Object.class, null);
110      } else {
111        constant = MethodHandles.constant(constantValue.getClass(), constantValue);
112      }
113      constant = MethodHandles.dropArguments(constant, 0,  type.parameterArray());
114      callSite.setTarget(constant.asType(type));
115      return constantValue;
116    } else {
117      MethodHandle guard = GUARD.bindTo(targetFunctionReference);
118      MethodHandle root = guardWithTest(guard, invoker, callSite.fallback);
119      callSite.setTarget(root);
120      return invoker.invokeWithArguments(args);
121    }
122  }
123
124  private static MethodHandle reorderArguments(String[] parameterNames, MethodHandle handle, String[] argumentNames) {
125    return NamedArgumentsHelper.reorderArguments(
126        "closure " + Arrays.toString(parameterNames),
127        Arrays.asList(parameterNames),
128        handle,
129        argumentNames, 1, 1);
130  }
131}