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