aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
blob: c2259896d13f2e168ac2f8aba925707f6a43ef25 (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
 * Copyright (c) 2010, 2013, 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.nashorn.internal.runtime;

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.Compiler;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.FunctionSignature;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;

/**
 * This is a subclass that represents a script function that may be regenerated,
 * for example with specialization based on call site types, or lazily generated.
 * The common denominator is that it can get new invokers during its lifespan,
 * unlike {@link FinalScriptFunctionData}
 */
public final class RecompilableScriptFunctionData extends ScriptFunctionData {

    private FunctionNode functionNode;
    private final PropertyMap  allocatorMap;
    private final CodeInstaller<ScriptEnvironment> installer;
    private final String allocatorClassName;

    /** lazily generated allocator */
    private MethodHandle allocator;

    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

    /**
     * Constructor - public as scripts use it
     *
     * @param functionNode       functionNode that represents this function code
     * @param installer          installer for code regeneration versions of this function
     * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
     * @param allocatorMap       allocator map to seed instances with, when constructing
     */
    public RecompilableScriptFunctionData(final FunctionNode functionNode, final CodeInstaller<ScriptEnvironment> installer, final String allocatorClassName, final PropertyMap allocatorMap) {
        super(functionNode.isAnonymous() ?
                "" :
                functionNode.getIdent().getName(),
              functionNode.getParameters().size(),
              functionNode.isStrict(),
              false,
              true);

        this.functionNode       = functionNode;
        this.installer          = installer;
        this.allocatorClassName = allocatorClassName;
        this.allocatorMap       = allocatorMap;
    }

    @Override
    String toSource() {
        final Source source = functionNode.getSource();
        final long   token  = tokenFor(functionNode);

        if (source != null && token != 0) {
            return source.getString(Token.descPosition(token), Token.descLength(token));
        }

        return "function " + (name == null ? "" : name) + "() { [native code] }";
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        final Source source = functionNode.getSource();
        final long   token  = tokenFor(functionNode);

        if (source != null) {
            sb.append(source.getName())
                .append(':')
                .append(source.getLine(Token.descPosition(token)))
                .append(' ');
        }

        return sb.toString() + super.toString();
    }

    private static long tokenFor(final FunctionNode fn) {
        final int  position   = Token.descPosition(fn.getFirstToken());
        final int  length     = Token.descPosition(fn.getLastToken()) - position + Token.descLength(fn.getLastToken());

        return Token.toDesc(TokenType.FUNCTION, position, length);
    }

    @Override
    ScriptObject allocate() {
        try {
            ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
            return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap);
        } catch (final RuntimeException | Error e) {
            throw e;
        } catch (final Throwable t) {
            throw new RuntimeException(t);
        }
    }

    private void ensureHasAllocator() throws ClassNotFoundException {
        if (allocator == null && allocatorClassName != null) {
            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
        }
    }

    @Override
    protected void ensureCodeGenerated() {
         if (!code.isEmpty()) {
             return; // nothing to do, we have code, at least some.
         }

         // check if function node is lazy, need to compile it.
         // note that currently function cloning is not working completely, which
         // means that the compiler will mutate the function node it has been given
         // once it has been compiled, it cannot be recompiled. This means that
         // lazy compilation works (not compiled yet) but e.g. specializations won't
         // until the copy-on-write changes for IR are in, making cloning meaningless.
         // therefore, currently method specialization is disabled. TODO

         if (functionNode.isLazy()) {
             Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
             final Compiler compiler = new Compiler(installer, functionNode);
             functionNode = compiler.compile();
             assert !functionNode.isLazy();
             compiler.install();

             // we don't need to update any flags - varArgs and needsCallee are instrincic
             // in the function world we need to get a destination node from the compile instead
             // and replace it with our function node. TODO
         }

         // we can't get here unless we have bytecode, either from eager compilation or from
         // running a lazy compile on the lines above

         assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);

         // code exists - look it up and add it into the automatically sorted invoker list
         code.add(
            new CompiledFunction(
                MH.findStatic(
                    LOOKUP,
                    functionNode.getCompileUnit().getCode(),
                    functionNode.getName(),
                    new FunctionSignature(functionNode).
                        getMethodType())));
    }

    @Override
    MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
        final MethodHandle mh = super.getBestInvoker(callSiteType, args);
        if (code.isLessSpecificThan(callSiteType)) {
            // opportunity for code specialization - we can regenerate a better version of this method
        }
        return mh;
    }

}