diff --git a/js/src/jsapi.c b/js/src/jsapi.c index 2ee93b11d322..74242ccd27b8 100644 --- a/js/src/jsapi.c +++ b/js/src/jsapi.c @@ -904,8 +904,8 @@ JS_EndRequest(JSContext *cx) * If js_DropObjectMap returns null, we held the last ref to scope. * The waiting thread(s) must have been killed, after which the GC * collected the object that held this scope. Unlikely, because it - * requires that the GC ran (e.g., from a branch callback) during - * this request, but possible. + * requires that the GC ran (e.g., from an operation callback) + * during this request, but possible. */ if (js_DropObjectMap(cx, &scope->map, NULL)) { js_InitLock(&scope->lock); @@ -4929,13 +4929,79 @@ JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc, return ok; } +JS_PUBLIC_API(void) +JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback, + uint32 operationLimit) +{ + JS_ASSERT(callback); + JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT); + JS_ASSERT(operationLimit > 0); + + cx->operationCount = (int32) operationLimit; + cx->operationLimit = operationLimit; + cx->operationCallbackIsSet = 1; + cx->operationCallback = callback; +} + +JS_PUBLIC_API(void) +JS_ClearOperationCallback(JSContext *cx) +{ + cx->operationCount = (int32) JS_MAX_OPERATION_LIMIT; + cx->operationLimit = JS_MAX_OPERATION_LIMIT; + cx->operationCallbackIsSet = 0; + cx->operationCallback = NULL; +} + +JS_PUBLIC_API(JSOperationCallback) +JS_GetOperationCallback(JSContext *cx) +{ + JS_ASSERT(cx->operationCallbackIsSet || !cx->operationCallback); + return cx->operationCallback; +} + +JS_PUBLIC_API(uint32) +JS_GetOperationLimit(JSContext *cx) +{ + JS_ASSERT(cx->operationCallbackIsSet); + return cx->operationLimit; +} + +JS_PUBLIC_API(void) +JS_SetOperationLimit(JSContext *cx, uint32 operationLimit) +{ + JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT); + JS_ASSERT(operationLimit > 0); + JS_ASSERT(cx->operationCallbackIsSet); + + cx->operationLimit = operationLimit; + if (cx->operationCount > (int32) operationLimit) + cx->operationCount = (int32) operationLimit; +} + JS_PUBLIC_API(JSBranchCallback) JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb) { JSBranchCallback oldcb; - oldcb = cx->branchCallback; - cx->branchCallback = cb; + if (cx->operationCallbackIsSet) { +#ifdef DEBUG + fprintf(stderr, +"JS API usage error: call to JS_SetOperationCallback is followed by\n" +"invocation of deprecated JS_SetBranchCallback\n"); + JS_ASSERT(0); +#endif + cx->operationCallbackIsSet = 0; + oldcb = NULL; + } else { + oldcb = (JSBranchCallback) cx->operationCallback; + } + if (cb) { + cx->operationCount = JSOW_SCRIPT_JUMP; + cx->operationLimit = JSOW_SCRIPT_JUMP; + cx->operationCallback = (JSOperationCallback) cb; + } else { + JS_ClearOperationCallback(cx); + } return oldcb; } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index bf4ff1b500e2..33e6d6b37c15 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -563,7 +563,10 @@ JS_StringToVersion(const char *string); JS_SetBranchCallback may be called with a null script parameter, by native code - that loops intensively */ + that loops intensively. + Deprecated, use + JS_SetOperationCallback + instead */ #define JSOPTION_DONT_REPORT_UNCAUGHT \ JS_BIT(8) /* When returning from the outermost API call, prevent @@ -2096,6 +2099,56 @@ extern JS_PUBLIC_API(JSBool) JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc, jsval *argv, jsval *rval); +/* + * The maximum value of the operation limit to pass to JS_SetOperationCallback + * and JS_SetOperationLimit. + */ +#define JS_MAX_OPERATION_LIMIT ((uint32) 0x7FFFFFFF) + +/* + * Set the operation callback that the engine calls periodically after + * the internal operation count reaches the specified limit. + * + * When operationLimit is 4096, the callback will be called at least after + * each backward jump in the interpreter. To minimize the overhead of the + * callback invocation we suggest at least 100*4096 as a value for + * operationLimit. + */ +extern JS_PUBLIC_API(void) +JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback, + uint32 operationLimit); + +extern JS_PUBLIC_API(void) +JS_ClearOperationCallback(JSContext *cx); + +extern JS_PUBLIC_API(JSOperationCallback) +JS_GetOperationCallback(JSContext *cx); + +/* + * Get the operation limit associated with the operation callback. This API + * function may be called only when the result of JS_GetOperationCallback(cx) + * is not null. + */ +extern JS_PUBLIC_API(uint32) +JS_GetOperationLimit(JSContext *cx); + +/* + * Change the operation limit associated with the operation callback. This API + * function may be called only when the result of JS_GetOperationCallback(cx) + * is not null. + */ +extern JS_PUBLIC_API(void) +JS_SetOperationLimit(JSContext *cx, uint32 operationLimit); + +/* + * Note well: JS_SetBranchCallback is deprecated. It is similar to + * + * JS_SetOperationCallback(cx, callback, 4096, NULL); + * + * except that the callback will not be called from a long-running native + * function when JSOPTION_NATIVE_BRANCH_CALLBACK is not set and the top-most + * frame is native. + */ extern JS_PUBLIC_API(JSBranchCallback) JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb); @@ -2256,12 +2309,12 @@ JS_PUBLIC_API(JSBool) JS_CStringsAreUTF8(void); /* - * Update the value to be returned by JS_CStringsAreUTF8(). Once set, - * it can not be changed. Must be called before the first call to + * Update the value to be returned by JS_CStringsAreUTF8(). Once set, it + * can never be changed. This API must be called before the first call to * JS_NewRuntime. */ JS_PUBLIC_API(void) -JS_SetCStringsAreUTF8(); +JS_SetCStringsAreUTF8(void); /* * Character encoding support. diff --git a/js/src/jsarray.c b/js/src/jsarray.c index 9bea05ce2505..bdc79ffacc42 100644 --- a/js/src/jsarray.c +++ b/js/src/jsarray.c @@ -1250,9 +1250,10 @@ array_sort(JSContext *cx, uintN argc, jsval *vp) goto out; if (!all_strings) { /* - * Do not call the branch callback in the following moving loop - * to make it fast and unroot the cached results of toString - * invocations before the callback has a chance to run the GC. + * We want to make the following loop fast and to unroot the + * cached results of toString invocations before the operation + * callback has a chance to run the GC. For this reason we do + * not call JS_CHECK_OPERATION_LIMIT in the loop. */ i = 0; do { diff --git a/js/src/jscntxt.c b/js/src/jscntxt.c index a3cf9e0bc8b3..3761f3742b10 100644 --- a/js/src/jscntxt.c +++ b/js/src/jscntxt.c @@ -232,6 +232,7 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize) memset(cx, 0, sizeof *cx); cx->runtime = rt; + JS_ClearOperationCallback(cx); cx->debugHooks = &rt->globalDebugHooks; #if JS_STACK_GROWTH_DIRECTION > 0 cx->stackLimit = (jsuword)-1; @@ -1309,10 +1310,26 @@ js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) } JSBool -js_ResetOperationCounter(JSContext *cx) +js_ResetOperationCount(JSContext *cx) { - JS_ASSERT(cx->operationCounter & JSOW_BRANCH_CALLBACK); + JSScript *script; - cx->operationCounter = 0; - return !cx->branchCallback || cx->branchCallback(cx, NULL); + JS_ASSERT(cx->operationCount <= 0); + JS_ASSERT(cx->operationLimit > 0); + + cx->operationCount = (int32) cx->operationLimit; + if (cx->operationCallbackIsSet) + return cx->operationCallback(cx); + + if (cx->operationCallback) { + /* + * Invoke the deprecated branch callback. It may be called only when + * the top-most frame is scripted or JSOPTION_NATIVE_BRANCH_CALLBACK + * is set. + */ + script = cx->fp ? cx->fp->script : NULL; + if (script || JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK)) + return ((JSBranchCallback) cx->operationCallback)(cx, script); + } + return JS_TRUE; } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index fafffb421160..dc59b0dbc837 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -637,8 +637,11 @@ struct JSContext { /* JSRuntime contextList linkage. */ JSCList links; - /* Counter of operations for branch callback calls. */ - uint32 operationCounter; + /* + * Operation count. It is declared early in the structure as a frequently + * accessed field. + */ + int32 operationCount; #if JS_HAS_XML_SUPPORT /* @@ -732,10 +735,18 @@ struct JSContext { void *tracefp; #endif - /* Per-context optional user callbacks. */ - JSBranchCallback branchCallback; + /* Per-context optional error reporter. */ JSErrorReporter errorReporter; + /* + * Flag indicating that the operation callback is set. When the flag is 0 + * but operationCallback is not null, operationCallback stores the branch + * callback. + */ + uint32 operationCallbackIsSet : 1; + uint32 operationLimit : 31; + JSOperationCallback operationCallback; + /* Interpreter activation count. */ uintN interpLevel; @@ -843,9 +854,6 @@ class JSAutoTempValueRooter #define JS_HAS_XML_OPTION(cx) ((cx)->version & JSVERSION_HAS_XML || \ JSVERSION_NUMBER(cx) >= JSVERSION_1_6) -#define JS_HAS_NATIVE_BRANCH_CALLBACK_OPTION(cx) \ - JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK) - /* * Initialize a library-wide thread private data index, and remember that it * has already been done, so that it happens only once ever. Returns true on @@ -1026,6 +1034,39 @@ extern JSErrorFormatString js_ErrorFormatString[JSErr_Limit]; # define JS_CHECK_STACK_SIZE(cx, lval) ((jsuword)&(lval) > (cx)->stackLimit) #endif +/* + * Update the operation counter according to the given weight and call the + * operation callback when we reach the operation limit. To make this + * frequently executed macro faster we decrease the counter from + * JSContext.operationLimit and compare against zero to check the limit. + * + * This macro can run the full GC. Return true if it is OK to continue and + * false otherwise. + */ +#define JS_CHECK_OPERATION_LIMIT(cx, weight) \ + (JS_CHECK_OPERATION_WEIGHT(weight), \ + (((cx)->operationCount -= (weight)) > 0 || js_ResetOperationCount(cx))) + +/* + * A version of JS_CHECK_OPERATION_LIMIT that just updates the operation count + * without calling the operation callback or any other API. This macro resets + * the count to 0 when it becomes negative to prevent a wrap-around when the + * macro is called repeatably. + */ +#define JS_COUNT_OPERATION(cx, weight) \ + ((void)(JS_CHECK_OPERATION_WEIGHT(weight), \ + (cx)->operationCount = ((cx)->operationCount > 0) \ + ? (cx)->operationCount - (weight) \ + : 0)) + +/* + * The implementation of the above macros assumes that subtracting weights + * twice from a positive number does not wrap-around INT32_MIN. + */ +#define JS_CHECK_OPERATION_WEIGHT(weight) \ + (JS_ASSERT((uint32) (weight) > 0), \ + JS_ASSERT((uint32) (weight) < JS_BIT(30))) + /* Relative operations weights. */ #define JSOW_JUMP 1 #define JSOW_ALLOCATION 100 @@ -1034,43 +1075,15 @@ extern JSErrorFormatString js_ErrorFormatString[JSErr_Limit]; #define JSOW_SET_PROPERTY 20 #define JSOW_NEW_PROPERTY 200 #define JSOW_DELETE_PROPERTY 30 - -#define JSOW_BRANCH_CALLBACK JS_BIT(12) +#define JSOW_ENTER_SHARP 4096 +#define JSOW_SCRIPT_JUMP 4096 /* - * The implementation of JS_COUNT_OPERATION macro below assumes that - * JSOW_BRANCH_CALLBACK is a power of two to ensures that an unsigned int - * overflow does not bring the counter below JSOW_BRANCH_CALLBACK limit. - */ -JS_STATIC_ASSERT((JSOW_BRANCH_CALLBACK & (JSOW_BRANCH_CALLBACK - 1)) == 0); - -/* - * Update the operation counter according the specified weight. This macro - * does not call the branch callback or any API. - */ -#define JS_COUNT_OPERATION(cx, weight) \ - ((void)(JS_ASSERT((weight) > 0), \ - JS_ASSERT((weight) <= JSOW_BRANCH_CALLBACK), \ - (cx)->operationCounter = (((cx)->operationCounter + (weight)) | \ - (~(JSOW_BRANCH_CALLBACK - 1) & \ - (cx)->operationCounter)))) - -/* - * Update the operation counter and call the branch callback when it reaches - * JSOW_BRANCH_CALLBACK limit. This macro can run the full GC. Return true if - * it is OK to continue and false otherwise. - */ -#define JS_CHECK_OPERATION_LIMIT(cx, weight) \ - (JS_COUNT_OPERATION(cx, weight), \ - ((cx)->operationCounter < JSOW_BRANCH_CALLBACK || \ - js_ResetOperationCounter(cx))) - -/* - * Reset the operation counter and call branch callback assuming that the + * Reset the operation count and call the operation callback assuming that the * operation limit is reached. */ extern JSBool -js_ResetOperationCounter(JSContext *cx); +js_ResetOperationCount(JSContext *cx); JS_END_EXTERN_C diff --git a/js/src/jsgc.h b/js/src/jsgc.h index b129b351b443..f3fafd909665 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -268,7 +268,8 @@ typedef struct JSGCStats { #endif uint32 alloc; /* number of allocation attempts */ uint32 retry; /* allocation attempt retries after running the GC */ - uint32 retryhalt; /* allocation retries halted by the branch callback */ + uint32 retryhalt; /* allocation retries halted by the operation + callback */ uint32 fail; /* allocation failures */ uint32 finalfail; /* finalizer calls allocator failures */ uint32 lockborn; /* things born locked */ diff --git a/js/src/jsinterp.c b/js/src/jsinterp.c index a376baa80603..8d341b4cb08e 100644 --- a/js/src/jsinterp.c +++ b/js/src/jsinterp.c @@ -1983,9 +1983,10 @@ js_Interpret(JSContext *cx, jsbytecode *pc, jsval *result) */ #define CHECK_BRANCH(len) \ JS_BEGIN_MACRO \ - if (len <= 0 && cx->branchCallback) { \ + if (len <= 0 && (cx->operationCount -= JSOW_SCRIPT_JUMP) <= 0) { \ SAVE_SP_AND_PC(fp); \ - if (!(ok = cx->branchCallback(cx, script))) \ + ok = js_ResetOperationCount(cx); \ + if (!ok) \ goto out; \ } \ JS_END_MACRO @@ -2323,9 +2324,6 @@ interrupt: depth = (jsint) script->depth; atoms = script->atomMap.vector; pc = fp->pc; -#if !JS_THREADED_INTERP - endpc = script->code + script->length; -#endif /* Store the generating pc for the return value. */ vp[-depth] = (jsval)pc; @@ -3773,9 +3771,6 @@ interrupt: /* Push the frame and set interpreter registers. */ cx->fp = fp = &newifp->frame; pc = script->code; -#if !JS_THREADED_INTERP - endpc = pc + script->length; -#endif inlineCallCount++; JS_RUNTIME_METER(rt, inlineCalls); diff --git a/js/src/jsiter.c b/js/src/jsiter.c index f81554b212b0..93be2ab1efc3 100644 --- a/js/src/jsiter.c +++ b/js/src/jsiter.c @@ -920,7 +920,7 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, } /* - * An error, silent termination by branch callback or an exception. + * An error, silent termination by operation callback or an exception. * Propagate the condition to the caller. */ return JS_FALSE; diff --git a/js/src/jsobj.c b/js/src/jsobj.c index de98cc2926d3..30949c0d1808 100644 --- a/js/src/jsobj.c +++ b/js/src/jsobj.c @@ -558,11 +558,8 @@ js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap, char buf[20]; size_t len; - if (JS_HAS_NATIVE_BRANCH_CALLBACK_OPTION(cx) && - cx->branchCallback && - !cx->branchCallback(cx, NULL)) { + if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_ENTER_SHARP)) return NULL; - } /* Set to null in case we return an early error. */ *sp = NULL; diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 8e40901039be..cb4546ccf93f 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -641,6 +641,12 @@ typedef JSBool typedef void (* JS_DLL_CALLBACK JSTraceDataOp)(JSTracer *trc, void *data); +typedef JSBool +(* JS_DLL_CALLBACK JSOperationCallback)(JSContext *cx); + +/* + * Deprecated form of JSOperationCallback. + */ typedef JSBool (* JS_DLL_CALLBACK JSBranchCallback)(JSContext *cx, JSScript *script);