23 * questions. |
23 * questions. |
24 */ |
24 */ |
25 |
25 |
26 package jdk.nashorn.internal.codegen; |
26 package jdk.nashorn.internal.codegen; |
27 |
27 |
|
28 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; |
28 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; |
29 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; |
29 |
30 |
30 import java.util.Arrays; |
31 import java.util.Arrays; |
31 import java.util.EnumSet; |
32 import java.util.EnumSet; |
32 import jdk.nashorn.internal.codegen.types.Type; |
33 import jdk.nashorn.internal.codegen.types.Type; |
33 import jdk.nashorn.internal.ir.Symbol; |
34 import jdk.nashorn.internal.ir.Symbol; |
34 import jdk.nashorn.internal.runtime.ScriptObject; |
35 import jdk.nashorn.internal.runtime.ScriptObject; |
|
36 import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; |
|
37 import jdk.nashorn.internal.runtime.options.Options; |
35 |
38 |
36 /** |
39 /** |
37 * A scope call or get operation that can be shared by several callsites. This generates a static |
40 * A scope call or get operation that can be shared by several call sites. This generates a static |
38 * method that wraps the invokedynamic instructions to get or call scope variables. |
41 * method that wraps the invokedynamic instructions to get or call scope variables. |
39 * The rationale for this is that initial linking of invokedynamic callsites is expensive, |
42 * The reason for this is to reduce memory footprint and initial linking overhead of huge scripts. |
40 * so by sharing them we can reduce startup overhead and allow very large scripts to run that otherwise wouldn't. |
43 * |
41 * |
44 * <p>Static methods generated by this class expect three parameters in addition to the parameters of the |
42 * <p>Static methods generated by this class expect two parameters in addition to the parameters of the |
45 * function call: The current scope object, the depth of the target scope relative to the scope argument, |
43 * function call: The current scope object and the depth of the target scope relative to the scope argument |
46 * and the program point in case the target operation is optimistic.</p> |
44 * for when this is known at compile-time (fast-scope access).</p> |
47 * |
45 * |
48 * <p>Optimistic operations are called with program point <code>0</code>. If an <code>UnwarrentedOptimismException</code> |
46 * <p>The second argument may be -1 for non-fast-scope symbols, in which case the scope chain is checked |
49 * is thrown, it is caught by the shared call method and rethrown with the program point of the invoking call site.</p> |
47 * for each call. This may cause callsite invalidation when the shared method is used from different |
50 * |
48 * scopes, but such sharing of non-fast scope calls may still be necessary for very large scripts.</p> |
51 * <p>Shared scope calls are not used if the scope contains a <code>with</code> statement or a call to |
49 * |
52 * <code>eval</code>.</p> |
50 * <p>Scope calls must not be shared between normal callsites and callsites contained in a <tt>with</tt> |
|
51 * statement as this condition is not handled by current guards and will cause a runtime error.</p> |
|
52 */ |
53 */ |
53 class SharedScopeCall { |
54 class SharedScopeCall { |
54 |
55 |
55 /** Threshold for using shared scope calls with fast scope access. */ |
56 /** |
56 public static final int FAST_SCOPE_CALL_THRESHOLD = 4; |
57 * Threshold for using shared scope function calls. |
57 /** Threshold for using shared scope calls with slow scope access. */ |
58 */ |
58 public static final int SLOW_SCOPE_CALL_THRESHOLD = 500; |
59 public static final int SHARED_CALL_THRESHOLD = |
59 /** Threshold for using shared scope gets with fast scope access. */ |
60 Options.getIntProperty("nashorn.shared.scope.call.threshold", 5); |
60 public static final int FAST_SCOPE_GET_THRESHOLD = 200; |
61 /** |
61 |
62 * Threshold for using shared scope variable getter. This is higher than for calls as lower values |
62 final Type valueType; |
63 * degrade performance on many scripts. |
63 final Symbol symbol; |
64 */ |
64 final Type returnType; |
65 public static final int SHARED_GET_THRESHOLD = |
65 final Type[] paramTypes; |
66 Options.getIntProperty("nashorn.shared.scope.get.threshold", 100); |
66 final int flags; |
67 |
67 final boolean isCall; |
68 private static final CompilerConstants.Call REPLACE_PROGRAM_POINT = virtualCallNoLookup( |
|
69 UnwarrantedOptimismException.class, "replaceProgramPoint", |
|
70 UnwarrantedOptimismException.class, int.class); |
|
71 |
|
72 /** Number of fixed parameters */ |
|
73 private static final int FIXED_PARAM_COUNT = 3; |
|
74 |
|
75 private final Type valueType; |
|
76 private final Symbol symbol; |
|
77 private final Type returnType; |
|
78 private final Type[] paramTypes; |
|
79 private final int flags; |
|
80 private final boolean isCall; |
|
81 private final boolean isOptimistic; |
68 private CompileUnit compileUnit; |
82 private CompileUnit compileUnit; |
69 private String methodName; |
83 private String methodName; |
70 private String staticSignature; |
84 private String staticSignature; |
71 |
85 |
72 /** |
86 /** |
75 * @param symbol the symbol |
89 * @param symbol the symbol |
76 * @param valueType the type of the value |
90 * @param valueType the type of the value |
77 * @param returnType the return type |
91 * @param returnType the return type |
78 * @param paramTypes the function parameter types |
92 * @param paramTypes the function parameter types |
79 * @param flags the callsite flags |
93 * @param flags the callsite flags |
80 */ |
94 * @param isOptimistic whether target call is optimistic and we need to handle UnwarrentedOptimismException |
81 SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { |
95 */ |
|
96 SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, |
|
97 final int flags, final boolean isOptimistic) { |
82 this.symbol = symbol; |
98 this.symbol = symbol; |
83 this.valueType = valueType; |
99 this.valueType = valueType; |
84 this.returnType = returnType; |
100 this.returnType = returnType; |
85 this.paramTypes = paramTypes; |
101 this.paramTypes = paramTypes; |
86 assert (flags & CALLSITE_OPTIMISTIC) == 0; |
|
87 this.flags = flags; |
102 this.flags = flags; |
88 // If paramTypes is not null this is a call, otherwise it's just a get. |
103 this.isCall = paramTypes != null; // If paramTypes is not null this is a call, otherwise it's just a get. |
89 this.isCall = paramTypes != null; |
104 this.isOptimistic = isOptimistic; |
90 } |
105 } |
91 |
106 |
92 @Override |
107 @Override |
93 public int hashCode() { |
108 public int hashCode() { |
94 return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags; |
109 return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags ^ Boolean.hashCode(isOptimistic); |
95 } |
110 } |
96 |
111 |
97 @Override |
112 @Override |
98 public boolean equals(final Object obj) { |
113 public boolean equals(final Object obj) { |
99 if (obj instanceof SharedScopeCall) { |
114 if (obj instanceof SharedScopeCall) { |
100 final SharedScopeCall c = (SharedScopeCall) obj; |
115 final SharedScopeCall c = (SharedScopeCall) obj; |
101 return symbol.equals(c.symbol) |
116 return symbol.equals(c.symbol) |
102 && flags == c.flags |
117 && flags == c.flags |
103 && returnType.equals(c.returnType) |
118 && returnType.equals(c.returnType) |
104 && Arrays.equals(paramTypes, c.paramTypes); |
119 && Arrays.equals(paramTypes, c.paramTypes) |
|
120 && isOptimistic == c.isOptimistic; |
105 } |
121 } |
106 return false; |
122 return false; |
107 } |
123 } |
108 |
124 |
109 /** |
125 /** |
138 // access, or -1 if this is not known at compile time (e.g. because of a "with" or "eval"). |
153 // access, or -1 if this is not known at compile time (e.g. because of a "with" or "eval"). |
139 |
154 |
140 final MethodEmitter method = classEmitter.method(methodFlags, methodName, getStaticSignature()); |
155 final MethodEmitter method = classEmitter.method(methodFlags, methodName, getStaticSignature()); |
141 method.begin(); |
156 method.begin(); |
142 |
157 |
143 // Load correct scope by calling getProto() on the scope argument as often as specified |
158 // Load correct scope by calling getProto(int) on the scope argument with the supplied depth argument |
144 // by the second argument. |
|
145 final Label parentLoopStart = new Label("parent_loop_start"); |
|
146 final Label parentLoopDone = new Label("parent_loop_done"); |
|
147 method.load(Type.OBJECT, 0); |
159 method.load(Type.OBJECT, 0); |
148 method.label(parentLoopStart); |
|
149 method.load(Type.INT, 1); |
160 method.load(Type.INT, 1); |
150 method.iinc(1, -1); |
161 method.invoke(ScriptObject.GET_PROTO_DEPTH); |
151 method.ifle(parentLoopDone); |
162 |
152 method.invoke(ScriptObject.GET_PROTO); |
163 assert !isCall || valueType.isObject(); // Callables are always loaded as object |
153 method._goto(parentLoopStart); |
164 |
154 method.label(parentLoopDone); |
165 // Labels for catch of UnsupportedOptimismException |
155 |
166 final Label beginTry; |
156 assert !isCall || valueType.isObject(); // Callables are always objects |
167 final Label endTry; |
157 // If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously |
168 final Label catchLabel; |
158 // only apply to the call. |
169 |
159 method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall, false); |
170 if(isOptimistic) { |
|
171 beginTry = new Label("begin_try"); |
|
172 endTry = new Label("end_try"); |
|
173 catchLabel = new Label("catch_label"); |
|
174 method.label(beginTry); |
|
175 method._try(beginTry, endTry, catchLabel, UnwarrantedOptimismException.class, false); |
|
176 } else { |
|
177 beginTry = endTry = catchLabel = null; |
|
178 } |
|
179 |
|
180 // If this is an optimistic get we set the optimistic flag but don't set the program point, |
|
181 // which implies a program point of 0. If optimism fails we'll replace it with the actual |
|
182 // program point which caller supplied as third argument. |
|
183 final int getFlags = isOptimistic && !isCall ? flags | CALLSITE_OPTIMISTIC : flags; |
|
184 method.dynamicGet(valueType, symbol.getName(), getFlags, isCall, false); |
160 |
185 |
161 // If this is a get we're done, otherwise call the value as function. |
186 // If this is a get we're done, otherwise call the value as function. |
162 if (isCall) { |
187 if (isCall) { |
163 method.convert(Type.OBJECT); |
188 method.convert(Type.OBJECT); |
164 // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. |
189 // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. |
165 method.loadUndefined(Type.OBJECT); |
190 method.loadUndefined(Type.OBJECT); |
166 int slot = 2; |
191 int slot = FIXED_PARAM_COUNT; |
167 for (final Type type : paramTypes) { |
192 for (final Type type : paramTypes) { |
168 method.load(type, slot); |
193 method.load(type, slot); |
169 slot += type.getSlots(); |
194 slot += type.getSlots(); |
170 } |
195 } |
171 // Shared scope calls disabled in optimistic world. TODO is this right? |
196 |
172 method.dynamicCall(returnType, 2 + paramTypes.length, flags, symbol.getName()); |
197 // Same as above, set optimistic flag but leave program point as 0. |
|
198 final int callFlags = isOptimistic ? flags | CALLSITE_OPTIMISTIC : flags; |
|
199 |
|
200 method.dynamicCall(returnType, 2 + paramTypes.length, callFlags, symbol.getName()); |
|
201 |
|
202 } |
|
203 |
|
204 if (isOptimistic) { |
|
205 method.label(endTry); |
173 } |
206 } |
174 |
207 |
175 method._return(returnType); |
208 method._return(returnType); |
|
209 |
|
210 if (isOptimistic) { |
|
211 // We caught a UnwarrantedOptimismException, replace 0 program point with actual program point |
|
212 method._catch(catchLabel); |
|
213 method.load(Type.INT, 2); |
|
214 method.invoke(REPLACE_PROGRAM_POINT); |
|
215 method.athrow(); |
|
216 } |
|
217 |
176 method.end(); |
218 method.end(); |
177 } |
219 } |
178 |
220 |
179 private String getStaticSignature() { |
221 private String getStaticSignature() { |
180 if (staticSignature == null) { |
222 if (staticSignature == null) { |
181 if (paramTypes == null) { |
223 if (paramTypes == null) { |
182 staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT); |
224 staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT, Type.INT); |
183 } else { |
225 } else { |
184 final Type[] params = new Type[paramTypes.length + 2]; |
226 final Type[] params = new Type[paramTypes.length + FIXED_PARAM_COUNT]; |
185 params[0] = Type.typeFor(ScriptObject.class); |
227 params[0] = Type.typeFor(ScriptObject.class); |
186 params[1] = Type.INT; |
228 params[1] = Type.INT; |
187 System.arraycopy(paramTypes, 0, params, 2, paramTypes.length); |
229 params[2] = Type.INT; |
|
230 System.arraycopy(paramTypes, 0, params, FIXED_PARAM_COUNT, paramTypes.length); |
188 staticSignature = Type.getMethodDescriptor(returnType, params); |
231 staticSignature = Type.getMethodDescriptor(returnType, params); |
189 } |
232 } |
190 } |
233 } |
191 return staticSignature; |
234 return staticSignature; |
192 } |
235 } |