[PATCH 0/2] MR11211: Draft: vbscript: Cache host DISPIDs as entries in the unified global tree.
Identifier lookups that resolve into a named item's host dispatch called GetIDsOfNames on the host for every access, while native vbscript resolves a name once and reuses the DISPID afterwards. Building on the unified member tree, collapse the lookup_global_vars and lookup_global_funcs pair into a single member lookup that also understands host properties, and record a resolved host DISPID as a SCRIPTDISP_HOSTPROP entry in the named item's script dispatch. A subsequent lookup finds the entry directly and never queries the host dispatch again. The entry lives and dies with the named item's dispatch, and is kept out of the script dispatch's own member enumeration. A later Dim or declaration of the same name shadows the cached host property, matching native precedence. This replaces the separate per-named-item DISPID cache (!11136) with a single representation in the global tree. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11211
From: Francis De Brabandere <francisdb@gmail.com> Identifier lookups that resolve into a named item's host dispatch called GetIDsOfNames on the host for every access, while native vbscript resolves a name once and reuses the DISPID afterwards. Building on the unified member tree, collapse the lookup_global_vars and lookup_global_funcs pair into a single member lookup that also understands host properties, and record a resolved host DISPID as a SCRIPTDISP_HOSTPROP entry in the named item's script dispatch. A subsequent lookup finds the entry directly and never queries the host dispatch again. The entry lives and dies with the named item's dispatch, and is kept out of the script dispatch's own member enumeration. A later Dim or declaration of the same name shadows the cached host property, matching native precedence. This replaces the separate per-named-item DISPID cache with a single representation in the global tree. --- dlls/vbscript/interp.c | 63 +++++++++++++++--------- dlls/vbscript/tests/vbscript.c | 90 ++++++++++++++++++++++++++++++++++ dlls/vbscript/vbdisp.c | 40 +++++++++++++-- dlls/vbscript/vbscript.c | 8 ++- dlls/vbscript/vbscript.h | 9 +++- 5 files changed, 180 insertions(+), 30 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 6d107eb2419..abb340fc334 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -97,34 +97,40 @@ static BOOL lookup_dynamic_vars(dynamic_var_t *var, const WCHAR *name, ref_t *re return FALSE; } -static BOOL lookup_global_vars(ScriptDisp *script, const WCHAR *name, ref_t *ref) +static void ref_from_entry(scriptdisp_entry_t *member, ref_t *ref) { - dynamic_var_t *var = script_disp_find_var(script, name); - - if(!var) - return FALSE; - - ref->type = var->is_const ? REF_CONST : REF_VAR; - ref->u.v = &var->v; - return TRUE; + switch(member->type) { + case SCRIPTDISP_VAR: + ref->type = member->u.var->is_const ? REF_CONST : REF_VAR; + ref->u.v = &member->u.var->v; + break; + case SCRIPTDISP_FUNC: + ref->type = REF_FUNC; + ref->u.f = member->u.func; + break; + case SCRIPTDISP_HOSTPROP: + ref->type = REF_DISP; + ref->u.d.disp = member->u.host.disp; + ref->u.d.id = member->u.host.id; + break; + } } -static BOOL lookup_global_funcs(ScriptDisp *script, const WCHAR *name, ref_t *ref) +/* A global name maps to a single member, so one lookup covers variables, + functions and host properties cached through a named item's dispatch. */ +static scriptdisp_entry_t *lookup_global_member(ScriptDisp *script, const WCHAR *name, ref_t *ref) { - function_t *func = script_disp_find_func(script, name); + scriptdisp_entry_t *member = script_disp_find_member(script, name); - if(func) { - ref->type = REF_FUNC; - ref->u.f = func; - return TRUE; - } - - return FALSE; + if(member) + ref_from_entry(member, ref); + return member; } static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_t invoke_type, ref_t *ref) { ScriptDisp *script_obj = ctx->script->script_obj; + scriptdisp_entry_t *member; named_item_t *item; unsigned i; DISPID id; @@ -202,9 +208,7 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ } if(ctx->code->named_item) { - if(lookup_global_vars(ctx->code->named_item->script_obj, name, ref)) - return S_OK; - if(lookup_global_funcs(ctx->code->named_item->script_obj, name, ref)) + if(lookup_global_member(ctx->code->named_item->script_obj, name, ref)) return S_OK; } @@ -213,6 +217,10 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ { hres = disp_get_id(ctx->func->code_ctx->named_item->disp, name, invoke_type, TRUE, &id); if(SUCCEEDED(hres)) { + /* Cache the host property in the item's tree; subsequent lookups + find it directly and never query the host dispatch again. */ + script_disp_add_hostprop(ctx->code->named_item->script_obj, name, + ctx->func->code_ctx->named_item->disp, id); ref->type = REF_DISP; ref->u.d.disp = ctx->func->code_ctx->named_item->disp; ref->u.d.id = id; @@ -220,9 +228,7 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ } } - if(lookup_global_vars(script_obj, name, ref)) - return S_OK; - if(lookup_global_funcs(script_obj, name, ref)) + if(lookup_global_member(script_obj, name, ref)) return S_OK; hres = get_builtin_id(ctx->script->global_obj, name, &id); @@ -242,8 +248,17 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ LIST_FOR_EACH_ENTRY(item, &ctx->script->named_items, named_item_t, entry) { if((item->flags & SCRIPTITEM_GLOBALMEMBERS)) { + if(item->script_obj && (member = script_disp_find_member(item->script_obj, name)) + && member->type == SCRIPTDISP_HOSTPROP) { + ref->type = REF_DISP; + ref->u.d.disp = member->u.host.disp; + ref->u.d.id = member->u.host.id; + return S_OK; + } hres = disp_get_id(item->disp, name, invoke_type, FALSE, &id); if(SUCCEEDED(hres)) { + if(item->script_obj) + script_disp_add_hostprop(item->script_obj, name, item->disp, id); ref->type = REF_DISP; ref->u.d.disp = item->disp; ref->u.d.id = id; diff --git a/dlls/vbscript/tests/vbscript.c b/dlls/vbscript/tests/vbscript.c index 6fe7893d3b0..9162435c7b8 100644 --- a/dlls/vbscript/tests/vbscript.c +++ b/dlls/vbscript/tests/vbscript.c @@ -304,15 +304,19 @@ static const IDispatchVtbl persistent_named_item_vtbl = { static IDispatch persistent_named_item = { &persistent_named_item_vtbl }; +static int shadow_method_dispid_queries, shadow_prop_dispid_queries; + static HRESULT WINAPI shadowing_GetIDsOfNames(IDispatch *iface, REFIID riid, LPOLESTR *names, UINT name_cnt, LCID lcid, DISPID *ids) { ok(name_cnt == 1, "name_cnt = %u\n", name_cnt); if(!wcscmp(names[0], L"shadowMethod")) { + shadow_method_dispid_queries++; *ids = 1; return S_OK; } if(!wcscmp(names[0], L"shadowProp")) { + shadow_prop_dispid_queries++; *ids = 2; return S_OK; } @@ -3080,6 +3084,91 @@ static void test_named_item_no_dim_routes_to_host(void) ok(!ref, "ref = %ld\n", ref); } +static void test_named_item_dispid_caching(void) +{ + IActiveScriptParse *parse; + IActiveScript *script; + HRESULT hres; + LONG ref; + + /* The engine resolves a host member name through GetIDsOfNames once and + * reuses the DISPID for subsequent lookups, within and across script + * chunks. */ + + script = create_vbscript(); + hres = IActiveScript_QueryInterface(script, &IID_IActiveScriptParse, (void**)&parse); + ok(hres == S_OK, "Could not get IActiveScriptParse: %08lx\n", hres); + + SET_EXPECT(GetLCID); + hres = IActiveScript_SetScriptSite(script, &ActiveScriptSite); + ok(hres == S_OK, "SetScriptSite failed: %08lx\n", hres); + CHECK_CALLED(GetLCID); + + hres = IActiveScript_AddNamedItem(script, L"shadowingItem", SCRIPTITEM_ISVISIBLE); + ok(hres == S_OK, "AddNamedItem failed: %08lx\n", hres); + + SET_EXPECT(OnStateChange_INITIALIZED); + hres = IActiveScriptParse_InitNew(parse); + ok(hres == S_OK, "InitNew failed: %08lx\n", hres); + CHECK_CALLED(OnStateChange_INITIALIZED); + + SET_EXPECT(OnStateChange_CONNECTED); + hres = IActiveScript_SetScriptState(script, SCRIPTSTATE_CONNECTED); + ok(hres == S_OK, "SetScriptState failed: %08lx\n", hres); + CHECK_CALLED(OnStateChange_CONNECTED); + + shadow_method_dispid_queries = 0; + shadow_prop_dispid_queries = 0; + + SET_EXPECT(OnEnterScript); + SET_EXPECT(OnLeaveScript); + SET_EXPECT(host_shadow_propput); + SET_EXPECT(host_shadow_propget); + SET_EXPECT(host_shadow_method); + hres = IActiveScriptParse_ParseScriptText(parse, L"shadowProp = 5\nx = shadowProp\nshadowMethod\n", + L"shadowingItem", NULL, NULL, 0, 0, 0, NULL, NULL); + ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); + CHECK_CALLED(OnEnterScript); + CHECK_CALLED(OnLeaveScript); + CHECK_CALLED(host_shadow_propput); + CHECK_CALLED(host_shadow_propget); + CHECK_CALLED(host_shadow_method); + + ok(shadow_prop_dispid_queries == 1, "shadowProp resolved %d times, expected 1\n", shadow_prop_dispid_queries); + ok(shadow_method_dispid_queries == 1, "shadowMethod resolved %d times, expected 1\n", shadow_method_dispid_queries); + + /* a later script chunk reuses the cached ids too */ + SET_EXPECT(OnEnterScript); + SET_EXPECT(OnLeaveScript); + SET_EXPECT(host_shadow_propput); + SET_EXPECT(host_shadow_method); + hres = IActiveScriptParse_ParseScriptText(parse, L"shadowProp = 6\nshadowMethod\n", + L"shadowingItem", NULL, NULL, 0, 0, 0, NULL, NULL); + ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); + CHECK_CALLED(OnEnterScript); + CHECK_CALLED(OnLeaveScript); + CHECK_CALLED(host_shadow_propput); + CHECK_CALLED(host_shadow_method); + + ok(shadow_prop_dispid_queries == 1, "shadowProp resolved %d times after second chunk, expected 1\n", + shadow_prop_dispid_queries); + ok(shadow_method_dispid_queries == 1, "shadowMethod resolved %d times after second chunk, expected 1\n", + shadow_method_dispid_queries); + + SET_EXPECT(OnStateChange_DISCONNECTED); + SET_EXPECT(OnStateChange_INITIALIZED); + SET_EXPECT(OnStateChange_CLOSED); + hres = IActiveScript_Close(script); + ok(hres == S_OK, "Close failed: %08lx\n", hres); + CHECK_CALLED(OnStateChange_DISCONNECTED); + CHECK_CALLED(OnStateChange_INITIALIZED); + CHECK_CALLED(OnStateChange_CLOSED); + + IActiveScriptParse_Release(parse); + ref = IActiveScript_Release(script); + ok(!ref, "ref = %ld\n", ref); +} + static void test_const_at_top_level(void) { IActiveScriptParse *parse; @@ -3521,6 +3610,7 @@ START_TEST(vbscript) test_named_item_sub_shadowing(); test_named_item_dim_shadowing(); test_named_item_no_dim_routes_to_host(); + test_named_item_dispid_caching(); test_const_at_top_level(); test_cross_parse_name_redef(); test_scriptdisp(); diff --git a/dlls/vbscript/vbdisp.c b/dlls/vbscript/vbdisp.c index 790dad2604d..e99e24e831d 100644 --- a/dlls/vbscript/vbdisp.c +++ b/dlls/vbscript/vbdisp.c @@ -82,6 +82,36 @@ scriptdisp_entry_t *script_disp_add_func(ScriptDisp *disp, function_t *func) return member; } +/* Cache a host property resolved through a named item's dispatch. Returns NULL + without caching when a real variable or function already owns the name, so a + later script declaration is never shadowed by a stale host entry. */ +scriptdisp_entry_t *script_disp_add_hostprop(ScriptDisp *disp, const WCHAR *name, IDispatch *disp_obj, DISPID id) +{ + scriptdisp_entry_t *member = script_disp_find_member(disp, name); + + if (member) { + if (member->type != SCRIPTDISP_HOSTPROP) + return NULL; + } else { + WCHAR *str; + size_t size = (lstrlenW(name) + 1) * sizeof(WCHAR); + + /* The name must outlive the entry; the caller's bytecode string may not, + so keep a copy in the dispatch's own pool. */ + if (!(str = heap_pool_alloc(&disp->heap, size))) + return NULL; + memcpy(str, name, size); + + if (!(member = script_disp_add_member(disp, str))) + return NULL; + } + + member->type = SCRIPTDISP_HOSTPROP; + member->u.host.disp = disp_obj; + member->u.host.id = id; + return member; +} + function_t *script_disp_find_func(ScriptDisp *disp, const WCHAR *name) { scriptdisp_entry_t *member = script_disp_find_member(disp, name); @@ -1785,11 +1815,15 @@ static HRESULT WINAPI ScriptDisp_GetDispID(IDispatchEx *iface, BSTR bstrName, DW member = script_disp_find_member(This, bstrName); if(member) { - if(member->type == SCRIPTDISP_FUNC) + /* Host properties cached on this dispatch are not script members. */ + if(member->type == SCRIPTDISP_FUNC) { *pid = member->u.func->index + 1 + DISPID_FUNCTION_MASK; - else + return S_OK; + } + if(member->type == SCRIPTDISP_VAR) { *pid = member->u.var->index + 1; - return S_OK; + return S_OK; + } } *pid = -1; diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index 66deef0de6c..a4cfeea9eab 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -145,8 +145,12 @@ HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res, BOOL /* Dim is permissive: it keeps an existing variable or function of the same name. A const from a previous compile unit is the exception: it is shadowed by a fresh variable that name lookups resolve to from now - on, while the defining compile unit keeps using the inlined value. */ - if (existing && (existing->type != SCRIPTDISP_VAR || !existing->u.var->is_const)) + on, while the defining compile unit keeps using the inlined value. A + cached host property is likewise shadowed by the fresh variable, which + outranks the host dispatch in the named item scope. */ + if (existing && existing->type == SCRIPTDISP_FUNC) + continue; + if (existing && existing->type == SCRIPTDISP_VAR && !existing->u.var->is_const) continue; if (!(var = heap_pool_alloc(&obj->heap, sizeof(*var)))) diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 287ff62c1f1..1626940e869 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -125,11 +125,13 @@ typedef struct _dynamic_var_t { typedef enum { SCRIPTDISP_VAR, SCRIPTDISP_FUNC, + SCRIPTDISP_HOSTPROP, } scriptdisp_entry_type_t; /* A single named member of a ScriptDisp. A global name resolves to exactly one member, so variables and functions share one tree; the payload selected - by type can grow to cover further kinds (e.g. cached host properties). */ + by type also caches a host property resolved through a named item's + dispatch, so a name found on the host is looked up only once. */ typedef struct { struct rb_entry entry; const WCHAR *name; @@ -137,6 +139,10 @@ typedef struct { union { dynamic_var_t *var; function_t *func; + struct { + IDispatch *disp; + DISPID id; + } host; } u; } scriptdisp_entry_t; @@ -166,6 +172,7 @@ typedef struct { scriptdisp_entry_t *script_disp_find_member(ScriptDisp *disp, const WCHAR *name); scriptdisp_entry_t *script_disp_add_var(ScriptDisp *disp, dynamic_var_t *var); scriptdisp_entry_t *script_disp_add_func(ScriptDisp *disp, function_t *func); +scriptdisp_entry_t *script_disp_add_hostprop(ScriptDisp *disp, const WCHAR *name, IDispatch *disp_obj, DISPID id); dynamic_var_t *script_disp_find_var(ScriptDisp *disp, const WCHAR *name); typedef struct _builtin_prop_t builtin_prop_t; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11211
From: Francis De Brabandere <francisdb@gmail.com> The compiler now allocates an extern slot per referenced global identifier and the OP_ident/OP_icall/OP_icallv bytecode addresses the slot by index instead of carrying the name string. Each slot caches its resolution on first use (a script global member entry or a builtin DISPID), stamped with a per-script generation counter that is bumped whenever a global member or named item is added, so stale slots are transparently re-resolved. A cached scriptdisp_entry_t pointer stays valid across re-declaration: the entry node is reused and only its payload is retagged, so re-reading the entry on each hit yields the current binding. The ctx-dependent shadowers (locals, dynamic vars, Me properties, caller frame) are still checked before the slot, so precedence is unchanged. --- dlls/vbscript/compile.c | 72 +++++++++++++++++++++++++++++++++++++-- dlls/vbscript/interp.c | 73 ++++++++++++++++++++++++++++++++-------- dlls/vbscript/vbdisp.c | 4 +++ dlls/vbscript/vbscript.c | 1 + dlls/vbscript/vbscript.h | 40 ++++++++++++++++++++-- 5 files changed, 171 insertions(+), 19 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 7e5278f91e9..629fb81bd1d 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -357,6 +357,73 @@ static HRESULT push_instr_bstr_uint(compile_ctx_t *ctx, vbsop_t op, const WCHAR return S_OK; } +/* Allocate an extern slot for a referenced global identifier. The bytecode + addresses the slot by index; the name is kept for the slow path. Each + reference gets its own slot (like the per-reference name strings the bytecode + carried before), so a name used from several sites caches independently + instead of paying a compile-time dedup scan that is quadratic in script + size. */ +static HRESULT alloc_extern(compile_ctx_t *ctx, const WCHAR *name, unsigned *ret) +{ + vbscode_t *code = ctx->code; + extern_ref_t *ext; + BSTR bstr; + + if(code->externs_cnt == code->externs_size) { + unsigned new_size = code->externs_size ? code->externs_size * 2 : 8; + ext = realloc(code->externs, new_size * sizeof(*ext)); + if(!ext) + return E_OUTOFMEMORY; + code->externs = ext; + code->externs_size = new_size; + } + + bstr = alloc_bstr_arg(ctx, name); + if(!bstr) + return E_OUTOFMEMORY; + + ext = &code->externs[code->externs_cnt]; + memset(ext, 0, sizeof(*ext)); + ext->name = bstr; + *ret = code->externs_cnt++; + return S_OK; +} + +static HRESULT push_instr_extern(compile_ctx_t *ctx, vbsop_t op, const WCHAR *name) +{ + unsigned slot, instr; + HRESULT hres; + + hres = alloc_extern(ctx, name, &slot); + if(FAILED(hres)) + return hres; + + instr = push_instr(ctx, op); + if(!instr) + return E_OUTOFMEMORY; + + instr_ptr(ctx, instr)->arg1.uint = slot; + return S_OK; +} + +static HRESULT push_instr_extern_uint(compile_ctx_t *ctx, vbsop_t op, const WCHAR *name, unsigned arg2) +{ + unsigned slot, instr; + HRESULT hres; + + hres = alloc_extern(ctx, name, &slot); + if(FAILED(hres)) + return hres; + + instr = push_instr(ctx, op); + if(!instr) + return E_OUTOFMEMORY; + + instr_ptr(ctx, instr)->arg1.uint = slot; + instr_ptr(ctx, instr)->arg2.uint = arg2; + return S_OK; +} + static HRESULT push_instr_uint_bstr(compile_ctx_t *ctx, vbsop_t op, unsigned arg1, const WCHAR *arg2) { unsigned instr; @@ -609,7 +676,7 @@ static HRESULT compile_member_call_expression(compile_ctx_t *ctx, member_express hres = push_instr_bstr_uint(ctx, ret_val ? OP_mcall : OP_mcallv, expr->identifier, arg_cnt); }else { - hres = push_instr_bstr_uint(ctx, ret_val ? OP_icall : OP_icallv, expr->identifier, arg_cnt); + hres = push_instr_extern_uint(ctx, ret_val ? OP_icall : OP_icallv, expr->identifier, arg_cnt); } return hres; @@ -639,7 +706,7 @@ static HRESULT compile_member_expression(compile_ctx_t *ctx, member_expression_t if(const_expr) return compile_expression(ctx, const_expr); - return push_instr_bstr(ctx, OP_ident, expr->identifier); + return push_instr_extern(ctx, OP_ident, expr->identifier); } static HRESULT compile_call_expression(compile_ctx_t *ctx, call_expression_t *expr, BOOL ret_val) @@ -2429,6 +2496,7 @@ void release_vbscode(vbscode_t *code) heap_pool_free(&code->heap); free(code->bstr_pool); + free(code->externs); free(code->source); free(code->instrs); free(code); diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index abb340fc334..d45b26967d8 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -117,7 +117,8 @@ static void ref_from_entry(scriptdisp_entry_t *member, ref_t *ref) } /* A global name maps to a single member, so one lookup covers variables, - functions and host properties cached through a named item's dispatch. */ + functions and cached host properties. Returns the entry (or NULL) so the + caller can cache it in an extern slot. */ static scriptdisp_entry_t *lookup_global_member(ScriptDisp *script, const WCHAR *name, ref_t *ref) { scriptdisp_entry_t *member = script_disp_find_member(script, name); @@ -127,7 +128,17 @@ static scriptdisp_entry_t *lookup_global_member(ScriptDisp *script, const WCHAR return member; } -static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_t invoke_type, ref_t *ref) +static inline void cache_extern_global(extern_ref_t *slot, exec_ctx_t *ctx, scriptdisp_entry_t *member) +{ + if(slot) { + slot->kind = EXTERN_GLOBAL; + slot->u.entry = member; + slot->gen = ctx->script->global_gen; + } +} + +static HRESULT lookup_identifier_ex(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_t invoke_type, + ref_t *ref, extern_ref_t *slot) { ScriptDisp *script_obj = ctx->script->script_obj; scriptdisp_entry_t *member; @@ -207,9 +218,30 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ return S_OK; } + /* Everything below resolves in global scope and is invariant for a given + code unit, so it can be cached in the extern slot. The slot is consulted + only after the ctx-dependent shadowers above, and is invalidated by the + generation counter whenever a global member or named item changes. */ + if(slot && slot->gen == ctx->script->global_gen) { + switch(slot->kind) { + case EXTERN_GLOBAL: + ref_from_entry(slot->u.entry, ref); + return S_OK; + case EXTERN_BUILTIN: + ref->type = REF_DISP; + ref->u.d.disp = &ctx->script->global_obj->IDispatch_iface; + ref->u.d.id = slot->u.builtin_id; + return S_OK; + case EXTERN_UNRESOLVED: + break; + } + } + if(ctx->code->named_item) { - if(lookup_global_member(ctx->code->named_item->script_obj, name, ref)) + if((member = lookup_global_member(ctx->code->named_item->script_obj, name, ref))) { + cache_extern_global(slot, ctx, member); return S_OK; + } } if(ctx->func->code_ctx->named_item && ctx->func->code_ctx->named_item->disp && @@ -228,11 +260,18 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ } } - if(lookup_global_member(script_obj, name, ref)) + if((member = lookup_global_member(script_obj, name, ref))) { + cache_extern_global(slot, ctx, member); return S_OK; + } hres = get_builtin_id(ctx->script->global_obj, name, &id); if(SUCCEEDED(hres)) { + if(slot) { + slot->kind = EXTERN_BUILTIN; + slot->u.builtin_id = id; + slot->gen = ctx->script->global_gen; + } ref->type = REF_DISP; ref->u.d.disp = &ctx->script->global_obj->IDispatch_iface; ref->u.d.id = id; @@ -271,6 +310,11 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ return S_OK; } +static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_t invoke_type, ref_t *ref) +{ + return lookup_identifier_ex(ctx, name, invoke_type, ref, NULL); +} + static HRESULT add_dynamic_var(exec_ctx_t *ctx, const WCHAR *name, BOOL is_const, VARIANT **out_var) { @@ -670,15 +714,16 @@ static HRESULT variant_call(exec_ctx_t *ctx, VARIANT *v, unsigned arg_cnt, VARIA return hres; } -static HRESULT do_icall(exec_ctx_t *ctx, VARIANT *res, BSTR identifier, unsigned arg_cnt, BOOL is_call) +static HRESULT do_icall(exec_ctx_t *ctx, VARIANT *res, extern_ref_t *slot, unsigned arg_cnt, BOOL is_call) { + BSTR identifier = slot->name; DISPPARAMS dp; ref_t ref; HRESULT hres; TRACE("%s %u\n", debugstr_w(identifier), arg_cnt); - hres = lookup_identifier(ctx, identifier, VBDISP_CALLGET, &ref); + hres = lookup_identifier_ex(ctx, identifier, VBDISP_CALLGET, &ref, slot); if(FAILED(hres)) return hres; @@ -748,14 +793,14 @@ static HRESULT do_icall(exec_ctx_t *ctx, VARIANT *res, BSTR identifier, unsigned static HRESULT interp_icall(exec_ctx_t *ctx) { - BSTR identifier = ctx->instr->arg1.bstr; + extern_ref_t *slot = &ctx->code->externs[ctx->instr->arg1.uint]; const unsigned arg_cnt = ctx->instr->arg2.uint; VARIANT v; HRESULT hres; TRACE("\n"); - hres = do_icall(ctx, &v, identifier, arg_cnt, TRUE); + hres = do_icall(ctx, &v, slot, arg_cnt, TRUE); if(FAILED(hres)) return hres; @@ -764,12 +809,12 @@ static HRESULT interp_icall(exec_ctx_t *ctx) static HRESULT interp_icallv(exec_ctx_t *ctx) { - BSTR identifier = ctx->instr->arg1.bstr; + extern_ref_t *slot = &ctx->code->externs[ctx->instr->arg1.uint]; const unsigned arg_cnt = ctx->instr->arg2.uint; TRACE("\n"); - return do_icall(ctx, NULL, identifier, arg_cnt, TRUE); + return do_icall(ctx, NULL, slot, arg_cnt, TRUE); } static HRESULT interp_vcall(exec_ctx_t *ctx) @@ -936,20 +981,20 @@ static HRESULT interp_local_prop(exec_ctx_t *ctx) static HRESULT interp_ident(exec_ctx_t *ctx) { - BSTR identifier = ctx->instr->arg1.bstr; + extern_ref_t *slot = &ctx->code->externs[ctx->instr->arg1.uint]; VARIANT v; HRESULT hres; - TRACE("%s\n", debugstr_w(identifier)); + TRACE("%s\n", debugstr_w(slot->name)); if((ctx->func->type == FUNC_FUNCTION || ctx->func->type == FUNC_PROPGET) - && !vbs_wcsicmp(identifier, ctx->func->name)) { + && !vbs_wcsicmp(slot->name, ctx->func->name)) { V_VT(&v) = VT_BYREF|VT_VARIANT; V_BYREF(&v) = &ctx->ret_val; return stack_push(ctx, &v); } - hres = do_icall(ctx, &v, identifier, 0, FALSE); + hres = do_icall(ctx, &v, slot, 0, FALSE); if(FAILED(hres)) return hres; diff --git a/dlls/vbscript/vbdisp.c b/dlls/vbscript/vbdisp.c index e99e24e831d..7fe551bc1e1 100644 --- a/dlls/vbscript/vbdisp.c +++ b/dlls/vbscript/vbdisp.c @@ -57,6 +57,7 @@ static scriptdisp_entry_t *script_disp_add_member(ScriptDisp *disp, const WCHAR return NULL; member->name = name; rb_put(&disp->members, name, &member->entry); + disp->ctx->global_gen++; return member; } @@ -1965,6 +1966,9 @@ HRESULT create_script_disp(script_ctx_t *ctx, ScriptDisp **ret) rb_init(&script_disp->members, scriptdisp_member_cmp); script_disp->rnd = 0x50000; + /* Invalidate any extern slots cached against a previous script dispatch. */ + ctx->global_gen++; + *ret = script_disp; return S_OK; } diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index a4cfeea9eab..218a3c08a54 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -271,6 +271,7 @@ static HRESULT insert_named_item(script_ctx_t *ctx, named_item_t *item) } list_add_tail(&ctx->named_items, &item->entry); + ctx->global_gen++; return S_OK; } diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 1626940e869..3ffdb211d59 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -146,6 +146,30 @@ typedef struct { } u; } scriptdisp_entry_t; +typedef enum { + EXTERN_UNRESOLVED, + EXTERN_GLOBAL, /* script global member (variable or function) */ + EXTERN_BUILTIN, /* builtin global function */ +} extern_ref_kind_t; + +/* A lazily-resolved reference to a global-scope identifier. The compiler + allocates one slot per referenced global name and the bytecode addresses it + by index instead of carrying the name string. The slot starts unresolved and + caches its resolution on first use, stamped with the script's global + resolution generation; adding a global member or named item bumps the + generation and so transparently invalidates stale slots. The name is kept for + the slow-path fallback. A cached scriptdisp_entry_t pointer stays valid across + re-declaration: the entry is reused, only its payload is retagged. */ +typedef struct { + BSTR name; + unsigned gen; + extern_ref_kind_t kind; + union { + scriptdisp_entry_t *entry; + DISPID builtin_id; + } u; +} extern_ref_t; + typedef struct { IDispatchEx IDispatchEx_iface; LONG ref; @@ -253,6 +277,11 @@ struct _script_ctx_t { exec_ctx_t *caller_exec; unsigned call_depth; + /* Bumped whenever global identifier resolution may change (a global member + or named item is added, or the script dispatch is recreated). Cached + extern slots compare against it to detect staleness. */ + unsigned global_gen; + EXCEPINFO ei; BSTR ei_identifier; vbscode_t *error_loc_code; @@ -317,9 +346,9 @@ typedef enum { X(exp, 1, 0, 0) \ X(gt, 1, ARG_UINT, 0) \ X(gteq, 1, ARG_UINT, 0) \ - X(icall, 1, ARG_BSTR, ARG_UINT) \ - X(icallv, 1, ARG_BSTR, ARG_UINT) \ - X(ident, 1, ARG_BSTR, 0) \ + X(icall, 1, ARG_UINT, ARG_UINT) \ + X(icallv, 1, ARG_UINT, ARG_UINT) \ + X(ident, 1, ARG_UINT, 0) \ X(idiv, 1, 0, 0) \ X(imp, 1, 0, 0) \ X(incc, 1, ARG_BSTR, 0) \ @@ -445,6 +474,11 @@ struct _vbscode_t { BSTR *bstr_pool; unsigned bstr_pool_size; unsigned bstr_cnt; + + extern_ref_t *externs; + unsigned externs_cnt; + unsigned externs_size; + heap_pool_t heap; function_t *funcs; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11211
Still needs a rebase on !11212 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11211#note_143747
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)