/** * Return a fast linked array getter, or null if we have to dispatch to super class * @param desc descriptor * @param request link request * @return invocation or null if needs to be sent to slow relink */ @Override public GuardedInvocation findFastGetIndexMethod(final Class<? extends ArrayData> clazz, final CallSiteDescriptor desc, final LinkRequest request) { final MethodType callType = desc.getMethodType(); final Class<?> indexType = callType.parameterType(1); final Class<?> returnType = callType.returnType(); if (ContinuousArrayData.class.isAssignableFrom(clazz) && indexType == int.class) { final Object[] args = request.getArguments(); final int index = (int)args[args.length - 1]; if (has(index)) { final MethodHandle getArray = ScriptObject.GET_ARRAY.methodHandle(); final int programPoint = NashornCallSiteDescriptor.isOptimistic(desc) ? NashornCallSiteDescriptor.getProgramPoint(desc) : INVALID_PROGRAM_POINT; MethodHandle getElement = getElementGetter(returnType, programPoint); if (getElement != null) { getElement = MH.filterArguments(getElement, 0, MH.asType(getArray, getArray.type().changeReturnType(clazz))); final MethodHandle guard = MH.insertArguments(FAST_ACCESS_GUARD, 0, clazz); return new GuardedInvocation(getElement, guard, (SwitchPoint)null, ClassCastException.class); } } } return null; }
/** * Return a fast linked array setter, or null if we have to dispatch to super class * @param desc descriptor * @param request link request * @return invocation or null if needs to be sent to slow relink */ @Override public GuardedInvocation findFastSetIndexMethod(final Class<? extends ArrayData> clazz, final CallSiteDescriptor desc, final LinkRequest request) { // array, index, value final MethodType callType = desc.getMethodType(); final Class<?> indexType = callType.parameterType(1); final Class<?> elementType = callType.parameterType(2); if (ContinuousArrayData.class.isAssignableFrom(clazz) && indexType == int.class) { final Object[] args = request.getArguments(); final int index = (int)args[args.length - 2]; if (hasRoomFor(index)) { MethodHandle setElement = getElementSetter(elementType); //Z(continuousarraydata, int, int), return true if successful if (setElement != null) { //else we are dealing with a wider type than supported by this callsite MethodHandle getArray = ScriptObject.GET_ARRAY.methodHandle(); getArray = MH.asType(getArray, getArray.type().changeReturnType(getClass())); setElement = MH.filterArguments(setElement, 0, getArray); final MethodHandle guard = MH.insertArguments(FAST_ACCESS_GUARD, 0, clazz); return new GuardedInvocation(setElement, guard, (SwitchPoint)null, ClassCastException.class); //CCE if not a scriptObject anymore } } } return null; }
/** * Find the appropriate GETINDEX method for an invoke dynamic call. * * @param desc the call site descriptor * @param request the link request * * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { final MethodType callType = desc.getMethodType(); final Class<?> returnType = callType.returnType(); final Class<?> returnClass = returnType.isPrimitive() ? returnType : Object.class; final Class<?> keyClass = callType.parameterType(1); final boolean explicitInstanceOfCheck = explicitInstanceOfCheck(desc, request); final String name; if (returnClass.isPrimitive()) { //turn e.g. get with a double into getDouble final String returnTypeName = returnClass.getName(); name = "get" + Character.toUpperCase(returnTypeName.charAt(0)) + returnTypeName.substring(1, returnTypeName.length()); } else { name = "get"; } final MethodHandle mh = findGetIndexMethodHandle(returnClass, name, keyClass, desc); return new GuardedInvocation(mh, getScriptObjectGuard(callType, explicitInstanceOfCheck), (SwitchPoint)null, explicitInstanceOfCheck ? null : ClassCastException.class); }
/** * Returns a pair of an invocation created with a passed-in supplier and a non-invalidated switch point for * optimistic assumptions (or null for the switch point if the function can not be deoptimized). While the method * makes a best effort to return a non-invalidated switch point (compensating for possible deoptimizing * recompilation happening on another thread) it is still possible that by the time this method returns the * switchpoint has been invalidated by a {@code RewriteException} triggered on another thread for this function. * This is not a problem, though, as these switch points are always used to produce call sites that fall back to * relinking when they are invalidated, and in this case the execution will end up here again. What this method * basically does is minimize such busy-loop relinking while the function is being recompiled on a different thread. * @param invocationSupplier the supplier that constructs the actual invocation method handle; should use the * {@code CompiledFunction} method itself in some capacity. * @return a tuple object containing the method handle as created by the supplier and an optimistic assumptions * switch point that is guaranteed to not have been invalidated before the call to this method (or null if the * function can't be further deoptimized). */ private synchronized HandleAndAssumptions getValidOptimisticInvocation(final Supplier<MethodHandle> invocationSupplier) { for(;;) { final MethodHandle handle = invocationSupplier.get(); final SwitchPoint assumptions = canBeDeoptimized() ? optimismInfo.optimisticAssumptions : null; if(assumptions != null && assumptions.hasBeenInvalidated()) { // We can be in a situation where one thread is in the middle of a deoptimizing compilation when we hit // this and thus, it has invalidated the old switch point, but hasn't created the new one yet. Note that // the behavior of invalidating the old switch point before recompilation, and only creating the new one // after recompilation is by design. If we didn't wait here for the recompilation to complete, we would // be busy looping through the fallback path of the invalidated switch point, relinking the call site // again with the same invalidated switch point, invoking the fallback, etc. stealing CPU cycles from // the recompilation task we're dependent on. This can still happen if the switch point gets invalidated // after we grabbed it here, in which case we'll indeed do one busy relink immediately. try { wait(); } catch (final InterruptedException e) { // Intentionally ignored. There's nothing meaningful we can do if we're interrupted } } else { return new HandleAndAssumptions(handle, assumptions); } } }
private static void relinkComposableInvoker(final CallSite cs, final CompiledFunction inv, final boolean constructor) { final HandleAndAssumptions handleAndAssumptions = inv.getValidOptimisticInvocation(new Supplier<MethodHandle>() { @Override public MethodHandle get() { return inv.getInvokerOrConstructor(constructor); } }); final MethodHandle handle = handleAndAssumptions.handle; final SwitchPoint assumptions = handleAndAssumptions.assumptions; final MethodHandle target; if(assumptions == null) { target = handle; } else { final MethodHandle relink = MethodHandles.insertArguments(RELINK_COMPOSABLE_INVOKER, 0, cs, inv, constructor); target = assumptions.guardWithTest(handle, MethodHandles.foldArguments(cs.dynamicInvoker(), relink)); } cs.setTarget(target.asType(cs.type())); }
boolean requestRecompile(final RewriteException e) { final Type retType = e.getReturnType(); final Type previousFailedType = invalidatedProgramPoints.put(e.getProgramPoint(), retType); if (previousFailedType != null && !previousFailedType.narrowerThan(retType)) { final StackTraceElement[] stack = e.getStackTrace(); final String functionId = stack.length == 0 ? data.getName() : stack[0].getClassName() + "." + stack[0].getMethodName(); log.info("RewriteException for an already invalidated program point ", e.getProgramPoint(), " in ", functionId, ". This is okay for a recursive function invocation, but a bug otherwise."); return false; } SwitchPoint.invalidateAll(new SwitchPoint[] { optimisticAssumptions }); return true; }
/** * Add a switchpoint to this guarded invocation * @param newSwitchPoint new switchpoint, or null for nop * @return new guarded invocation with the extra switchpoint */ public GuardedInvocation addSwitchPoint(final SwitchPoint newSwitchPoint) { if (newSwitchPoint == null) { return this; } final SwitchPoint[] newSwitchPoints; if (switchPoints != null) { newSwitchPoints = new SwitchPoint[switchPoints.length + 1]; System.arraycopy(switchPoints, 0, newSwitchPoints, 0, switchPoints.length); newSwitchPoints[switchPoints.length] = newSwitchPoint; } else { newSwitchPoints = new SwitchPoint[] { newSwitchPoint }; } return new GuardedInvocation(invocation, guard, newSwitchPoints, exception); }
/** * Get a switch point for a property with the given {@code name} that will be invalidated when * the property definition is changed in this object's prototype chain. Returns {@code null} if * the property is defined in this object itself. * * @param name the property name * @param owner the property owner, null if property is not defined * @return a SwitchPoint or null */ public final SwitchPoint[] getProtoSwitchPoints(final String name, final ScriptObject owner) { if (owner == this || getProto() == null) { return null; } final List<SwitchPoint> switchPoints = new ArrayList<>(); for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { final ScriptObject parent = obj.getProto(); parent.getMap().addListener(name, obj.getMap()); final SwitchPoint sp = parent.getMap().getSharedProtoSwitchPoint(); if (sp != null && !sp.hasBeenInvalidated()) { switchPoints.add(sp); } } switchPoints.add(getMap().getSwitchPoint(name)); return switchPoints.toArray(new SwitchPoint[0]); }
/** * Create a new guarded invocation with an added switch point. * @param newSwitchPoint new switch point. Can be null in which case this * method return the current guarded invocation with no changes. * @return a guarded invocation with the added switch point. */ public GuardedInvocation addSwitchPoint(final SwitchPoint newSwitchPoint) { if (newSwitchPoint == null) { return this; } final SwitchPoint[] newSwitchPoints; if (switchPoints != null) { newSwitchPoints = new SwitchPoint[switchPoints.length + 1]; System.arraycopy(switchPoints, 0, newSwitchPoints, 0, switchPoints.length); newSwitchPoints[switchPoints.length] = newSwitchPoint; } else { newSwitchPoints = new SwitchPoint[] { newSwitchPoint }; } return new GuardedInvocation(invocation, guard, newSwitchPoints, exception); }
private synchronized SwitchPoint getLexicalScopeSwitchPoint() { SwitchPoint switchPoint = lexicalScopeSwitchPoint; if (switchPoint == null || switchPoint.hasBeenInvalidated()) { switchPoint = lexicalScopeSwitchPoint = new SwitchPoint(); } return switchPoint; }
/** * Given a builtin object, traverse its properties recursively and associate them with a name that * will be a key to their invalidation switchpoint. * @param name name for key * @param func builtin script object */ private void tagBuiltinProperties(final String name, final ScriptObject func) { SwitchPoint sp = context.getBuiltinSwitchPoint(name); if (sp == null) { sp = context.newBuiltinSwitchPoint(name); } //get all builtin properties in this builtin object and register switchpoints keyed on the propery name, //one overwrite destroys all for now, e.g. Function.prototype.apply = 17; also destroys Function.prototype.call etc for (final jdk.nashorn.internal.runtime.Property prop : extractBuiltinProperties(name, func)) { prop.setBuiltinSwitchPoint(sp); } }
private Access getOrCreateSwitchPoint(final String name) { Access acc = map.get(name); if (acc != null) { return acc; } final SwitchPoint sp = new SwitchPoint(); map.put(name, acc = new Access(name, sp)); return acc; }
@SuppressWarnings("unused") private static Object invalidateSwitchPoint(final AccessorProperty property, final Object obj) { if (!property.builtinSwitchPoint.hasBeenInvalidated()) { SwitchPoint.invalidateAll(new SwitchPoint[] { property.builtinSwitchPoint }); } return obj; }
/** * Create a new builtin switchpoint and return it * @param name key name * @return new builtin switchpoint */ public SwitchPoint newBuiltinSwitchPoint(final String name) { assert builtinSwitchPoints.get(name) == null; final SwitchPoint sp = new BuiltinSwitchPoint(); builtinSwitchPoints.put(name, sp); return sp; }
private SwitchPoint findBuiltinSwitchPoint(final String key) { for (ScriptObject myProto = getProto(); myProto != null; myProto = myProto.getProto()) { final Property prop = myProto.getMap().findProperty(key); if (prop != null) { final SwitchPoint sp = prop.getBuiltinSwitchPoint(); if (sp != null && !sp.hasBeenInvalidated()) { return sp; } } } return null; }
/** * Get a switch point for a property with the given {@code name} that will be invalidated when * the property definition is changed in this object's prototype chain. Returns {@code null} if * the property is defined in this object itself. * * @param name the property name * @param owner the property owner, null if property is not defined * @return a SwitchPoint or null */ public final SwitchPoint getProtoSwitchPoint(final String name, final ScriptObject owner) { if (owner == this || getProto() == null) { return null; } for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { final ScriptObject parent = obj.getProto(); parent.getMap().addListener(name, obj.getMap()); } return getMap().getSwitchPoint(name); }
/** * Composes from its components an actual guarded invocation that represents the dynamic setter method for the property. * @return the composed guarded invocation that represents the dynamic setter method for the property. */ GuardedInvocation createGuardedInvocation() { // getGuard() and getException() either both return null, or neither does. The reason for that is that now // getGuard returns a map guard that casts its argument to ScriptObject, and if that fails, we need to // relink on ClassCastException. final boolean explicitInstanceOfCheck = NashornGuards.explicitInstanceOfCheck(desc, request); return new GuardedInvocation(methodHandle, NashornGuards.getGuard(sobj, property, desc, explicitInstanceOfCheck), (SwitchPoint)null, explicitInstanceOfCheck ? null : ClassCastException.class); }
private SetMethod createSetMethod(final SwitchPoint builtinSwitchPoint) { if (find != null) { return createExistingPropertySetter(); } checkStrictCreateNewVariable(); if (sobj.isScope()) { return createGlobalPropertySetter(); } return createNewPropertySetter(builtinSwitchPoint); }
private SetMethod createNewPropertySetter(final SwitchPoint builtinSwitchPoint) { final SetMethod sm = map.getFreeFieldSlot() > -1 ? createNewFieldSetter(builtinSwitchPoint) : createNewSpillPropertySetter(builtinSwitchPoint); final PropertyListeners listeners = map.getListeners(); if (listeners != null) { listeners.propertyAdded(sm.property); } return sm; }
private SetMethod createNewSetter(final Property property, final SwitchPoint builtinSwitchPoint) { property.setBuiltinSwitchPoint(builtinSwitchPoint); final PropertyMap oldMap = getMap(); final PropertyMap newMap = getNewMap(property); final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); //fast type specific setter final MethodHandle fastSetter = property.getSetter(type, newMap); //0 sobj, 1 value, slot folded for spill property already //slow setter, that calls ScriptObject.set with appropraite type and key name MethodHandle slowSetter = ScriptObject.SET_SLOW[getAccessorTypeIndex(type)]; slowSetter = MH.insertArguments(slowSetter, 3, NashornCallSiteDescriptor.getFlags(desc)); slowSetter = MH.insertArguments(slowSetter, 1, name); slowSetter = MH.asType(slowSetter, slowSetter.type().changeParameterType(0, Object.class)); assert slowSetter.type().equals(fastSetter.type()) : "slow=" + slowSetter + " != fast=" + fastSetter; //cas map used as guard, if true that means we can do the set fast MethodHandle casMap = MH.insertArguments(ScriptObject.CAS_MAP, 1, oldMap, newMap); casMap = MH.dropArguments(casMap, 1, type); casMap = MH.asType(casMap, casMap.type().changeParameterType(0, Object.class)); final MethodHandle casGuard = MH.guardWithTest(casMap, fastSetter, slowSetter); //outermost level needs an extendable check. if object can be extended, guard is true and //we can run the cas setter. The setter goes to "nop" VOID_RETURN if false or throws an //exception if we are in strict mode and object is not extensible MethodHandle extCheck = MH.insertArguments(ScriptObject.EXTENSION_CHECK, 1, isStrict, name); extCheck = MH.asType(extCheck, extCheck.type().changeParameterType(0, Object.class)); extCheck = MH.dropArguments(extCheck, 1, type); MethodHandle nop = JSType.VOID_RETURN.methodHandle(); nop = MH.dropArguments(nop, 0, Object.class, type); return new SetMethod(MH.asType(MH.guardWithTest(extCheck, casGuard, nop), fastSetter.type()), property); }
/** * Indicate that a prototype property has changed. * * @param property {@link Property} to invalidate. */ synchronized void invalidateProtoGetSwitchPoint(final Property property) { if (protoGetSwitches != null) { final String key = property.getKey(); final SwitchPoint sp = protoGetSwitches.get(key); if (sp != null) { protoGetSwitches.remove(key); if (Context.DEBUG) { protoInvalidations++; } SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); } } }
/** * Indicate that proto itself has changed in hierarchy somewhere. */ synchronized void invalidateAllProtoGetSwitchPoints() { if (protoGetSwitches != null && !protoGetSwitches.isEmpty()) { if (Context.DEBUG) { protoInvalidations += protoGetSwitches.size(); } SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[protoGetSwitches.values().size()])); protoGetSwitches.clear(); } }
/** * Returns true if and only if this guarded invocation has a switchpoint, and that switchpoint has been invalidated. * @return true if and only if this guarded invocation has a switchpoint, and that switchpoint has been invalidated. */ public boolean hasBeenInvalidated() { if (switchPoints == null) { return false; } for (final SwitchPoint sp : switchPoints) { if (sp.hasBeenInvalidated()) { return true; } } return false; }
/** * Composes the invocation, switchpoint, and the guard into a composite method handle that knows how to fall back. * @param switchpointFallback the fallback method handle in case switchpoint is invalidated. * @param guardFallback the fallback method handle in case guard returns false. * @param catchFallback the fallback method in case the exception handler triggers * @return a composite method handle. */ public MethodHandle compose(final MethodHandle guardFallback, final MethodHandle switchpointFallback, final MethodHandle catchFallback) { final MethodHandle guarded = guard == null ? invocation : MethodHandles.guardWithTest( guard, invocation, guardFallback); final MethodHandle catchGuarded = exception == null ? guarded : MH.catchException( guarded, exception, MethodHandles.dropArguments( catchFallback, 0, exception)); if (switchPoints == null) { return catchGuarded; } MethodHandle spGuarded = catchGuarded; for (final SwitchPoint sp : switchPoints) { spGuarded = sp.guardWithTest(spGuarded, switchpointFallback); } return spGuarded; }
/** * Composes the invocation, guard, switch points, and the exception into a * composite method handle that knows how to fall back when the guard fails * or the invocation is invalidated. * @param switchpointFallback the fallback method handle in case a switch * point is invalidated. * @param guardFallback the fallback method handle in case guard returns * false. * @param catchFallback the fallback method in case the exception handler * triggers. * @return a composite method handle. */ public MethodHandle compose(final MethodHandle guardFallback, final MethodHandle switchpointFallback, final MethodHandle catchFallback) { final MethodHandle guarded = guard == null ? invocation : MethodHandles.guardWithTest( guard, invocation, guardFallback); final MethodHandle catchGuarded = exception == null ? guarded : MethodHandles.catchException( guarded, exception, MethodHandles.dropArguments( catchFallback, 0, exception)); if (switchPoints == null) { return catchGuarded; } MethodHandle spGuarded = catchGuarded; for (final SwitchPoint sp : switchPoints) { spGuarded = sp.guardWithTest(spGuarded, switchpointFallback); } return spGuarded; }
private SwitchPoint findBuiltinSwitchPoint(final Object key) { for (ScriptObject myProto = getProto(); myProto != null; myProto = myProto.getProto()) { final Property prop = myProto.getMap().findProperty(key); if (prop != null) { final SwitchPoint sp = prop.getBuiltinSwitchPoint(); if (sp != null && !sp.hasBeenInvalidated()) { return sp; } } } return null; }
private SwitchPoint getProtoSwitchPoint(final String name) { if (getProto() == null) { return null; } for (ScriptObject obj = this; obj.getProto() != null; obj = obj.getProto()) { final ScriptObject parent = obj.getProto(); parent.getMap().addListener(name, obj.getMap()); } return getMap().getSwitchPoint(name); }
private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) { if (switchPoints != null) { for (final SwitchPoint switchPoint : switchPoints) { if (switchPoint.hasBeenInvalidated()) { return true; } } } return false; }