aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java
blob: 4d3df5093ed59136cc67ea259ef03d75f8521c51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package jdk.nashorn.internal.objects;

import static jdk.nashorn.internal.lookup.Lookup.MH;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import jdk.nashorn.internal.codegen.CompilationException;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.FunctionSignature;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptObject;

/**
 * A trampoline is a promise to compile a {@link ScriptFunction} later. It just looks like
 * the call to the script function, but when invoked it will compile the script function
 * (in a new compile unit) and invoke it
 */
public final class ScriptFunctionTrampolineImpl extends ScriptFunctionImpl {

    private CodeInstaller<ScriptEnvironment> installer;

    /** Function node to lazily recompile when trampoline is hit */
    private FunctionNode functionNode;

    /**
     * Constructor
     *
     * @param installer    opaque code installer from context
     * @param functionNode function node to lazily compile when trampoline is hit
     * @param data         {@link ScriptFunctionData} for function
     * @param scope        scope
     * @param allocator    allocator
     */
    public ScriptFunctionTrampolineImpl(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode, final ScriptFunctionData data, final ScriptObject scope, final MethodHandle allocator) {
        super(null, data, scope, allocator);

        this.installer    = installer;
        this.functionNode = functionNode;

        data.setMethodHandles(makeTrampoline(), allocator);
    }

    private final MethodHandle makeTrampoline() {
        final MethodType mt =
            new FunctionSignature(
                true,
                functionNode.needsCallee(),
                Type.OBJECT,
                functionNode.getParameters().size()).
            getMethodType();

        return
            MH.bindTo(
                MH.asCollector(
                    findOwnMH(
                        "trampoline",
                        Object.class,
                        Object[].class),
                    Object[].class,
                    mt.parameterCount()),
                this);
    }

    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findVirtual(MethodHandles.lookup(), ScriptFunctionTrampolineImpl.class, name, MH.type(rtype, types));
    }

    @Override
    protected ScriptFunction makeBoundFunction(final ScriptFunctionData data) {
        //prevent trampoline recompilation cycle if a function is bound before use
        compile();
        return super.makeBoundFunction(data);
    }

    private MethodHandle compile() throws CompilationException {
        final Compiler compiler = new Compiler(installer, functionNode);

        compiler.compile();

        final Class<?> clazz = compiler.install();
        /* compute function signature for lazy method. this can be done first after compilation, as only then do we know
         * the final state about callees, scopes and specialized parameter types */
        final FunctionSignature signature = new FunctionSignature(true, functionNode.needsCallee(), Type.OBJECT, functionNode.getParameters().size());
        final MethodType        mt        = signature.getMethodType();

        MethodHandle mh = MH.findStatic(MethodHandles.publicLookup(), clazz, functionNode.getName(), mt);
        if (functionNode.needsCallee()) {
            mh = MH.bindTo(mh, this);
        }

        // now the invoker method looks like the one our superclass is expecting
        resetInvoker(mh);

        return mh;
    }

    @SuppressWarnings("unused")
    private Object trampoline(final Object... args) throws CompilationException {
        Compiler.LOG.info(">>> TRAMPOLINE: Hitting trampoline for '" + functionNode.getName() + "'");
        MethodHandle mh = compile();

        Compiler.LOG.info("<<< COMPILED TO: " + mh);
        // spread the array to invididual args of the correct type
        mh = MH.asSpreader(mh, Object[].class, mh.type().parameterCount());

        try {
            //invoke the real method the trampoline points to. this only happens once
            return mh.invoke(args);
        } catch (final RuntimeException | Error e) {
            throw e;
        } catch (final Throwable t) {
            throw new RuntimeException(t);
        }
    }
}