From: Gabriel Ivăncescu gabrielopcode@gmail.com
The 'prototype' prop of a source function is, by default, an empty object with a 'constructor' prop pointing back to the function. Currently, every source function is created in this fashion, which makes it a circular reference and thus prevents it from being freed until the Garbage Collector kicks in.
The performance problem is that the function keeps a ref to the enclosing scope, and since the scope is being held by it, the engine will detach the scope, believing it to be used for the time being (until the GC cleans it). This can cause substantial performance issues for such a common case. The FFXIV Launcher, for example, leaks a large amount of such short-lived functions and the enclosing scopes.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 67 +++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 16 deletions(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 3b50a88eb77..53bf9911a0e 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -756,6 +756,55 @@ HRESULT create_builtin_constructor(script_ctx_t *ctx, builtin_invoke_t value_pro return S_OK; }
+/* + * Create the actual prototype on demand, since it is a circular ref, which prevents the vast + * majority of functions from being released quickly, leading to unnecessary scope detach. + */ +static HRESULT InterpretedFunction_get_prototype(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + jsdisp_t *prototype; + HRESULT hres; + + hres = create_object(ctx, NULL, &prototype); + if(FAILED(hres)) + return hres; + + hres = jsdisp_define_data_property(jsthis, L"prototype", PROPF_WRITABLE, jsval_obj(prototype)); + if(SUCCEEDED(hres)) + hres = set_constructor_prop(ctx, jsthis, prototype); + if(FAILED(hres)) { + jsdisp_release(prototype); + return hres; + } + + *r = jsval_obj(prototype); + return S_OK; +} + +static HRESULT InterpretedFunction_set_prototype(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t value) +{ + return jsdisp_define_data_property(jsthis, L"prototype", PROPF_WRITABLE, value); +} + +static const builtin_prop_t InterpretedFunction_props[] = { + {L"arguments", NULL, 0, Function_get_arguments}, + {L"length", NULL, 0, Function_get_length}, + {L"prototype", NULL, 0, InterpretedFunction_get_prototype, InterpretedFunction_set_prototype} +}; + +static const builtin_info_t InterpretedFunction_info = { + JSCLASS_FUNCTION, + Function_value, + ARRAY_SIZE(InterpretedFunction_props), + InterpretedFunction_props, + Function_destructor, + NULL, + NULL, + NULL, + NULL, + Function_gc_traverse +}; + static HRESULT InterpretedFunction_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, unsigned argc, jsval_t *argv, jsval_t *r) { @@ -848,24 +897,10 @@ HRESULT create_source_function(script_ctx_t *ctx, bytecode_t *code, function_cod scope_chain_t *scope_chain, jsdisp_t **ret) { InterpretedFunction *function; - jsdisp_t *prototype; HRESULT hres;
- hres = create_object(ctx, NULL, &prototype); - if(FAILED(hres)) - return hres; - - hres = create_function(ctx, NULL, &InterpretedFunctionVtbl, sizeof(InterpretedFunction), PROPF_CONSTR, - FALSE, NULL, (void**)&function); - if(SUCCEEDED(hres)) { - hres = jsdisp_define_data_property(&function->function.dispex, L"prototype", PROPF_WRITABLE, - jsval_obj(prototype)); - if(SUCCEEDED(hres)) - hres = set_constructor_prop(ctx, &function->function.dispex, prototype); - if(FAILED(hres)) - jsdisp_release(&function->function.dispex); - } - jsdisp_release(prototype); + hres = create_function(ctx, &InterpretedFunction_info, &InterpretedFunctionVtbl, sizeof(InterpretedFunction), + PROPF_CONSTR, FALSE, NULL, (void**)&function); if(FAILED(hres)) return hres;