[PATCH v16 0/8] MR10515: vbscript: Bind local variables, class properties, and loop variables at compile time.
Resolve identifiers to direct indices during compilation instead of doing runtime `wcsicmp` string scans in `lookup_identifier()`. This adds new opcodes that access local variables, function arguments, class properties, and loop variables by index, eliminating the linear string comparison overhead for the most common cases. **New opcodes:** - `OP_local` / `OP_assign_local` / `OP_set_local` - read/write local vars and args by index - `OP_local_prop` / `OP_assign_local_prop` / `OP_set_local_prop` - read/write class properties by index - `OP_step_local` / `OP_incc_local` - For-loop step/increment with indexed counter - `OP_enumnext_local` - For-Each iteration with indexed loop variable This mirrors jscript's `OP_local` / `bind_local()` optimization (`dlls/jscript/compile.c`) and addresses the FIXME in `lookup_identifier()` noting that class property access should be bound at compile time. Names that cannot be resolved at compile time (globals in Execute/ExecuteGlobal code, dynamic vars, host objects) continue to use the existing string-based path. The last commit also adds a fast path for SAFEARRAY iteration that bypasses the `IEnumVARIANT` COM vtable dispatch and copies elements directly from the array data, eliminating one intermediate `VariantCopy` per iteration. ### Local variable / argument / class property access (1M iterations, 10 reads per iteration) | Benchmark | Windows (VM) | Wine master | Wine (7 commits) | **Speedup** | vs Windows | |-----------|-------------|-------------|-------------------|---------|------------| | Local vars | 156 ms | 413 ms | 234 ms | **1.8x** | 1.5x slower | | Arguments | 171 ms | 357 ms | 231 ms | **1.5x** | 1.4x slower | | Class props | 265 ms | 393 ms | 226 ms | **1.7x** | 0.9x (faster!) | ### For-loop variants (10M iterations) | Benchmark | Windows (VM) | Wine master | Wine (7 commits) | **Speedup** | vs Windows | |-----------|-------------|-------------|-------------------|---------|------------| | Empty For | 117 ms | 370 ms | 270 ms | **1.4x** | 2.3x slower | | For + assign | 203 ms | 613 ms | 430 ms | **1.4x** | 2.1x slower | | Do-While manual | 437 ms | 646 ms | 513 ms | **1.3x** | 1.2x slower | | For Step 2 | 62 ms | 370 ms | 271 ms | **1.4x** | 4.4x slower | | Nested For | 125 ms | 387 ms | 268 ms | **1.4x** | 2.1x slower | | For local Sub | 109 ms | 294 ms | 251 ms | **1.2x** | 2.3x slower | | For R8 counter | 203 ms | 2,812 ms | 2,529 ms | **1.1x** | 12x slower | | For-Each array | 7 ms | 286 ms | 62 ms | **4.6x** | 8.9x slower | The remaining 4-13x gap vs Windows in For-loops is from `VarAdd`/`VarCmp` going through full `VariantChangeTypeEx` machinery (including locale alloc/free per call). A separate MR ([!10528](https://gitlab.winehq.org/wine/wine/-/merge_requests/10529)) adds I2/I4 fast paths in oleaut32 that bring integer loops within 1.2-1.9x of Windows when combined with this branch. -- v16: vbscript: Fast-path For-Each iteration over SAFEARRAY. vbscript: Bind For-Each loop variable at compile time. vbscript: Bind global-scope Dim variables at compile time. vbscript: Bind class properties at compile time. vbscript: Bind For-loop variables at compile time. vbscript: Bind local variable assignments at compile time. vbscript: Factor out assign_local_var from assign_ident. vbscript: Bind local variables and arguments at compile time. https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Resolve Dim variables and function arguments to direct indices during compilation, emitting OP_local instead of OP_ident. This eliminates runtime wcsicmp scans in lookup_identifier() for the most common case. This mirrors jscript's OP_local optimization (dlls/jscript/compile.c: bind_local / emit_identifier). --- dlls/vbscript/compile.c | 41 +++++++++++++++++++++++++++++++++++----- dlls/vbscript/interp.c | 21 ++++++++++++++++++++ dlls/vbscript/vbscript.h | 1 + 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 54314372a13..713834ee5e6 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -456,6 +456,34 @@ static BOOL lookup_dim_decls(compile_ctx_t *ctx, const WCHAR *name) return FALSE; } +/* Resolve an identifier to a local variable or argument index at compile time. + * Returns TRUE if bound, with *ret set to: non-negative = dim var index, negative = arg index (-1 = arg 0, etc.). + * This mirrors jscript's bind_local() / local_off() convention. */ +static BOOL bind_local(compile_ctx_t *ctx, const WCHAR *name, int *ret) +{ + dim_decl_t *dim_decl; + unsigned i; + + if(ctx->func->type == FUNC_GLOBAL) + return FALSE; + + for(dim_decl = ctx->dim_decls, i = 0; dim_decl; dim_decl = dim_decl->next, i++) { + if(!vbs_wcsicmp(dim_decl->name, name)) { + *ret = i; + return TRUE; + } + } + + for(i = 0; i < ctx->func->arg_cnt; i++) { + if(!vbs_wcsicmp(ctx->func->args[i].name, name)) { + *ret = -(int)i - 1; + return TRUE; + } + } + + return FALSE; +} + static HRESULT compile_args(compile_ctx_t *ctx, expression_t *args, unsigned *ret) { unsigned arg_cnt = 0; @@ -506,15 +534,18 @@ static HRESULT compile_member_call_expression(compile_ctx_t *ctx, member_express static HRESULT compile_member_expression(compile_ctx_t *ctx, member_expression_t *expr) { expression_t *const_expr; + int local_ref; if (expr->obj_expr) /* FIXME: we should probably have a dedicated opcode as well */ return compile_member_call_expression(ctx, expr, 0, TRUE); - if (!lookup_dim_decls(ctx, expr->identifier) && !lookup_args_name(ctx, expr->identifier)) { - const_expr = lookup_const_decls(ctx, expr->identifier, TRUE); - if(const_expr) - return compile_expression(ctx, const_expr); - } + if(bind_local(ctx, expr->identifier, &local_ref)) + return push_instr_int(ctx, OP_local, local_ref); + + const_expr = lookup_const_decls(ctx, expr->identifier, TRUE); + if(const_expr) + return compile_expression(ctx, const_expr); + return push_instr_bstr(ctx, OP_ident, expr->identifier); } diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index ba530fca38c..8804b13646b 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -873,6 +873,27 @@ static HRESULT interp_mcallv(exec_ctx_t *ctx) return do_mcall(ctx, NULL); } +static HRESULT interp_local(exec_ctx_t *ctx) +{ + const int arg = ctx->instr->arg1.lng; + VARIANT *v; + VARIANT r; + + if(arg < 0) + v = ctx->args - arg - 1; + else + v = ctx->vars + arg; + + TRACE("%s\n", debugstr_variant(v)); + + if(V_VT(v) == (VT_BYREF|VT_VARIANT)) + v = V_VARIANTREF(v); + + V_VT(&r) = VT_BYREF|VT_VARIANT; + V_BYREF(&r) = v; + return stack_push(ctx, &r); +} + static HRESULT interp_ident(exec_ctx_t *ctx) { BSTR identifier = ctx->instr->arg1.bstr; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 19c9260a342..353aab3a9e3 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -270,6 +270,7 @@ typedef enum { X(incc, 1, ARG_BSTR, 0) \ X(int, 1, ARG_INT, 0) \ X(is, 1, 0, 0) \ + X(local, 1, ARG_INT, 0) \ X(jmp, 0, ARG_ADDR, 0) \ X(jmp_false, 0, ARG_ADDR, 0) \ X(jmp_true, 0, ARG_ADDR, 0) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Extract the REF_VAR body of assign_ident into a new assign_local_var helper so that future compile-time-bound local assignments can reuse it instead of duplicating the logic. No functional change. --- dlls/vbscript/interp.c | 86 +++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 8804b13646b..af62c02680d 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -942,61 +942,63 @@ static HRESULT assign_value(exec_ctx_t *ctx, VARIANT *dst, VARIANT *src, WORD fl return S_OK; } -static HRESULT assign_ident(exec_ctx_t *ctx, BSTR name, WORD flags, DISPPARAMS *dp) +static HRESULT assign_local_var(exec_ctx_t *ctx, VARIANT *v, WORD flags, DISPPARAMS *dp) { - ref_t ref; HRESULT hres; - hres = lookup_identifier(ctx, name, VBDISP_LET, &ref); - if(FAILED(hres)) - return hres; + if(V_VT(v) == (VT_VARIANT|VT_BYREF)) + v = V_VARIANTREF(v); - switch(ref.type) { - case REF_VAR: { - VARIANT *v = ref.u.v; + if(arg_cnt(dp)) { + SAFEARRAY *array; - if(V_VT(v) == (VT_VARIANT|VT_BYREF)) - v = V_VARIANTREF(v); + if(V_VT(v) == VT_DISPATCH) + return disp_propput(ctx->script, V_DISPATCH(v), DISPID_VALUE, flags, dp); - if(arg_cnt(dp)) { - SAFEARRAY *array; + if(!(V_VT(v) & VT_ARRAY)) + return DISP_E_TYPEMISMATCH; - if(V_VT(v) == VT_DISPATCH) { - hres = disp_propput(ctx->script, V_DISPATCH(v), DISPID_VALUE, flags, dp); - break; - } + switch(V_VT(v)) { + case VT_ARRAY|VT_BYREF|VT_VARIANT: + array = *V_ARRAYREF(v); + break; + case VT_ARRAY|VT_VARIANT: + array = V_ARRAY(v); + break; + default: + FIXME("Unsupported array type %x\n", V_VT(v)); + return E_NOTIMPL; + } - if(!(V_VT(v) & VT_ARRAY)) - return DISP_E_TYPEMISMATCH; + if(!array) { + WARN("null array\n"); + return MAKE_VBSERROR(VBSE_OUT_OF_BOUNDS); + } - switch(V_VT(v)) { - case VT_ARRAY|VT_BYREF|VT_VARIANT: - array = *V_ARRAYREF(v); - break; - case VT_ARRAY|VT_VARIANT: - array = V_ARRAY(v); - break; - default: - FIXME("Unsupported array type %x\n", V_VT(v)); - return E_NOTIMPL; - } + hres = array_access(array, dp, &v); + if(FAILED(hres)) + return hres; + }else if(V_VT(v) == (VT_ARRAY|VT_BYREF|VT_VARIANT)) { + FIXME("non-array assign\n"); + return E_NOTIMPL; + } - if(!array) { - WARN("null array\n"); - return MAKE_VBSERROR(VBSE_OUT_OF_BOUNDS); - } + return assign_value(ctx, v, dp->rgvarg, flags); +} - hres = array_access(array, dp, &v); - if(FAILED(hres)) - return hres; - }else if(V_VT(v) == (VT_ARRAY|VT_BYREF|VT_VARIANT)) { - FIXME("non-array assign\n"); - return E_NOTIMPL; - } +static HRESULT assign_ident(exec_ctx_t *ctx, BSTR name, WORD flags, DISPPARAMS *dp) +{ + ref_t ref; + HRESULT hres; + + hres = lookup_identifier(ctx, name, VBDISP_LET, &ref); + if(FAILED(hres)) + return hres; - hres = assign_value(ctx, v, dp->rgvarg, flags); + switch(ref.type) { + case REF_VAR: + hres = assign_local_var(ctx, ref.u.v, flags, dp); break; - } case REF_DISP: hres = disp_propput(ctx->script, ref.u.d.disp, ref.u.d.id, flags, dp); break; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Emit OP_assign_local and OP_set_local instead of OP_assign_ident and OP_set_ident when the assignment target is a known local variable or function argument. This eliminates runtime lookup_identifier() calls for the most common assignment pattern. This mirrors jscript's OP_local optimization for identifier writes. --- dlls/vbscript/compile.c | 28 ++++++++++++++++++++++++ dlls/vbscript/interp.c | 46 ++++++++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 2 ++ 3 files changed, 76 insertions(+) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 713834ee5e6..890d024a276 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -192,6 +192,19 @@ static HRESULT push_instr_uint(compile_ctx_t *ctx, vbsop_t op, unsigned arg) return S_OK; } +static HRESULT push_instr_int_uint(compile_ctx_t *ctx, vbsop_t op, LONG arg1, unsigned arg2) +{ + unsigned ret; + + ret = push_instr(ctx, op); + if(!ret) + return E_OUTOFMEMORY; + + instr_ptr(ctx, ret)->arg1.lng = arg1; + instr_ptr(ctx, ret)->arg2.uint = arg2; + return S_OK; +} + static HRESULT push_instr_addr(compile_ctx_t *ctx, vbsop_t op, unsigned arg) { unsigned ret; @@ -1199,6 +1212,21 @@ static HRESULT compile_assignment(compile_ctx_t *ctx, expression_t *left, expres return hres; } + if(!member_expr->obj_expr) { + int local_ref; + if(bind_local(ctx, member_expr->identifier, &local_ref)) { + hres = push_instr_int_uint(ctx, is_set ? OP_set_local : OP_assign_local, + local_ref, args_cnt); + if(FAILED(hres)) + return hres; + + if(!emit_catch(ctx, 0)) + return E_OUTOFMEMORY; + + return S_OK; + } + } + hres = push_instr_bstr_uint(ctx, op, member_expr->identifier, args_cnt); if(FAILED(hres)) return hres; diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index af62c02680d..b87573674d7 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1070,6 +1070,52 @@ static HRESULT interp_set_ident(exec_ctx_t *ctx) return S_OK; } +static HRESULT interp_assign_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg1.lng; + const unsigned arg_cnt = ctx->instr->arg2.uint; + VARIANT *v; + DISPPARAMS dp; + HRESULT hres; + + v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + + TRACE("%d\n", ref); + + vbstack_to_dp(ctx, arg_cnt, TRUE, &dp); + hres = assign_local_var(ctx, v, DISPATCH_PROPERTYPUT, &dp); + if(FAILED(hres)) + return hres; + + stack_popn(ctx, arg_cnt+1); + return S_OK; +} + +static HRESULT interp_set_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg1.lng; + const unsigned arg_cnt = ctx->instr->arg2.uint; + VARIANT *v; + DISPPARAMS dp; + HRESULT hres; + + v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + + TRACE("%d %u\n", ref, arg_cnt); + + hres = stack_assume_disp(ctx, arg_cnt, NULL); + if(FAILED(hres)) + return hres; + + vbstack_to_dp(ctx, arg_cnt, TRUE, &dp); + hres = assign_local_var(ctx, v, DISPATCH_PROPERTYPUTREF, &dp); + if(FAILED(hres)) + return hres; + + stack_popn(ctx, arg_cnt + 1); + return S_OK; +} + static HRESULT interp_assign_member(exec_ctx_t *ctx) { BSTR identifier = ctx->instr->arg1.bstr; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 353aab3a9e3..53fcb3bb518 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -241,6 +241,7 @@ typedef enum { X(add, 1, 0, 0) \ X(and, 1, 0, 0) \ X(assign_ident, 1, ARG_BSTR, ARG_UINT) \ + X(assign_local, 1, ARG_INT, ARG_UINT) \ X(assign_member, 1, ARG_BSTR, ARG_UINT) \ X(bool, 1, ARG_INT, 0) \ X(catch, 1, ARG_ADDR, ARG_UINT) \ @@ -296,6 +297,7 @@ typedef enum { X(ret, 0, 0, 0) \ X(retval, 1, 0, 0) \ X(set_ident, 1, ARG_BSTR, ARG_UINT) \ + X(set_local, 1, ARG_INT, ARG_UINT) \ X(set_member, 1, ARG_BSTR, ARG_UINT) \ X(stack, 1, ARG_UINT, 0) \ X(step, 0, ARG_ADDR, ARG_BSTR) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Emit OP_step_local and OP_incc_local instead of OP_step and OP_incc when the loop counter is a known local variable or function argument. This eliminates runtime lookup_identifier() calls on every loop iteration. This mirrors jscript's approach of resolving locals at compile time. --- dlls/vbscript/compile.c | 37 ++++++++++++++++------ dlls/vbscript/interp.c | 68 ++++++++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 2 ++ 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 890d024a276..f2abed5cc82 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -917,8 +917,12 @@ static HRESULT compile_forto_statement(compile_ctx_t *ctx, forto_statement_t *st statement_ctx_t loop_ctx = {3}; unsigned step_instr, instr, expr_err_label, past_err_label, body_label, from_offset; BSTR identifier; + int local_ref; + BOOL is_local; HRESULT hres; + is_local = bind_local(ctx, stat->identifier, &local_ref); + identifier = alloc_bstr_arg(ctx, stat->identifier); if(!identifier) return E_OUTOFMEMORY; @@ -987,10 +991,17 @@ static HRESULT compile_forto_statement(compile_ctx_t *ctx, forto_statement_t *st if(!loop_ctx.for_end_label) return E_OUTOFMEMORY; - step_instr = push_instr(ctx, OP_step); - if(!step_instr) - return E_OUTOFMEMORY; - instr_ptr(ctx, step_instr)->arg2.bstr = identifier; + if(is_local) { + step_instr = push_instr(ctx, OP_step_local); + if(!step_instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, step_instr)->arg2.lng = local_ref; + }else { + step_instr = push_instr(ctx, OP_step); + if(!step_instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, step_instr)->arg2.bstr = identifier; + } instr_ptr(ctx, step_instr)->arg1.uint = loop_ctx.for_end_label; if(!emit_catch(ctx, 3)) @@ -1002,11 +1013,19 @@ static HRESULT compile_forto_statement(compile_ctx_t *ctx, forto_statement_t *st if(FAILED(hres)) return hres; - - instr = push_instr(ctx, OP_incc); - if(!instr) - return E_OUTOFMEMORY; - instr_ptr(ctx, instr)->arg1.bstr = identifier; + /* We need a separated OP_step here so that errors jump to the end-of-loop catch. */ + ctx->loc = stat->stat.loc; + if(is_local) { + instr = push_instr(ctx, OP_incc_local); + if(!instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, instr)->arg1.lng = local_ref; + }else { + instr = push_instr(ctx, OP_incc); + if(!instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, instr)->arg1.bstr = identifier; + } instr = push_instr(ctx, OP_step); if(!instr) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index b87573674d7..4b56d80d695 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1707,6 +1707,55 @@ loop_not_initialized: return hres; } +static HRESULT interp_step_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg2.lng; + VARIANT *v; + BOOL gteq_zero; + VARIANT zero; + HRESULT hres; + + TRACE("%d\n", ref); + + v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + + if(V_VT(stack_top(ctx, 0)) == VT_EMPTY && V_VT(stack_top(ctx, 1)) == VT_EMPTY) { + WARN("For loop not initialized\n"); + clear_ei(&ctx->script->ei); + ctx->script->ei.scode = MAKE_VBSERROR(VBSE_FOR_LOOP_NOT_INITIALIZED); + map_vbs_exception(&ctx->script->ei); + stack_popn(ctx, 3); + instr_jmp(ctx, ctx->instr->arg1.uint); + return S_OK; + } + + V_VT(&zero) = VT_I2; + V_I2(&zero) = 0; + hres = VarCmp(stack_top(ctx, 0), &zero, ctx->script->lcid, 0); + if(FAILED(hres)) + goto loop_not_initialized; + + gteq_zero = hres == VARCMP_GT || hres == VARCMP_EQ; + + hres = VarCmp(v, stack_top(ctx, 1), ctx->script->lcid, 0); + if(FAILED(hres)) + goto loop_not_initialized; + + if(hres == VARCMP_EQ || hres == (gteq_zero ? VARCMP_LT : VARCMP_GT)) { + ctx->instr++; + }else { + stack_popn(ctx, 3); + instr_jmp(ctx, ctx->instr->arg1.uint); + } + return S_OK; + +loop_not_initialized: + WARN("For loop not initialized\n"); + stack_popn(ctx, 2); + instr_jmp(ctx, ctx->instr->arg1.uint); + return hres; +} + static HRESULT interp_newenum(exec_ctx_t *ctx) { variant_val_t v; @@ -2635,6 +2684,25 @@ static HRESULT interp_incc(exec_ctx_t *ctx) return S_OK; } +static HRESULT interp_incc_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg1.lng; + VARIANT *lvar, v; + HRESULT hres; + + TRACE("%d\n", ref); + + lvar = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + + hres = VarAdd(stack_top(ctx, 0), lvar, &v); + if(FAILED(hres)) + return hres; + + VariantClear(lvar); + *lvar = v; + return S_OK; +} + static HRESULT interp_with(exec_ctx_t *ctx) { VARIANT *v; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 53fcb3bb518..73c1841823d 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -269,6 +269,7 @@ typedef enum { X(idiv, 1, 0, 0) \ X(imp, 1, 0, 0) \ X(incc, 1, ARG_BSTR, 0) \ + X(incc_local, 1, ARG_INT, 0) \ X(int, 1, ARG_INT, 0) \ X(is, 1, 0, 0) \ X(local, 1, ARG_INT, 0) \ @@ -301,6 +302,7 @@ typedef enum { X(set_member, 1, ARG_BSTR, ARG_UINT) \ X(stack, 1, ARG_UINT, 0) \ X(step, 0, ARG_ADDR, ARG_BSTR) \ + X(step_local, 0, ARG_ADDR, ARG_INT) \ X(stop, 1, 0, 0) \ X(string, 1, ARG_STR, 0) \ X(sub, 1, 0, 0) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Resolve class property names to direct indices during compilation of class methods, emitting OP_local_prop, OP_assign_local_prop, and OP_set_local_prop instead of the string-based OP_ident/OP_assign_ident/ OP_set_ident. This addresses the FIXME in lookup_identifier() that noted these should be bound while generating bytecode. This mirrors jscript's approach of resolving identifiers at compile time rather than doing runtime string comparisons. --- dlls/vbscript/compile.c | 54 +++++++++++++++++++++++++++++++++++++++ dlls/vbscript/interp.c | 55 ++++++++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 11 +++++--- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index f2abed5cc82..c969b9a0058 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -63,6 +63,7 @@ typedef struct { function_t *func; function_decl_t *func_decls; + dim_decl_t *class_props; } compile_ctx_t; static HRESULT compile_expression(compile_ctx_t*,expression_t*); @@ -205,6 +206,19 @@ static HRESULT push_instr_int_uint(compile_ctx_t *ctx, vbsop_t op, LONG arg1, un return S_OK; } +static HRESULT push_instr_uint_uint(compile_ctx_t *ctx, vbsop_t op, unsigned arg1, unsigned arg2) +{ + unsigned ret; + + ret = push_instr(ctx, op); + if(!ret) + return E_OUTOFMEMORY; + + instr_ptr(ctx, ret)->arg1.uint = arg1; + instr_ptr(ctx, ret)->arg2.uint = arg2; + return S_OK; +} + static HRESULT push_instr_addr(compile_ctx_t *ctx, vbsop_t op, unsigned arg) { unsigned ret; @@ -497,6 +511,26 @@ static BOOL bind_local(compile_ctx_t *ctx, const WCHAR *name, int *ret) return FALSE; } +/* Resolve an identifier to a class property index at compile time. + * Only valid when compiling inside a class method (ctx->class_props != NULL). */ +static BOOL bind_class_prop(compile_ctx_t *ctx, const WCHAR *name, unsigned *ret) +{ + dim_decl_t *prop; + unsigned i; + + if(!ctx->class_props) + return FALSE; + + for(prop = ctx->class_props, i = 0; prop; prop = prop->next, i++) { + if(!vbs_wcsicmp(prop->name, name)) { + *ret = i; + return TRUE; + } + } + + return FALSE; +} + static HRESULT compile_args(compile_ctx_t *ctx, expression_t *args, unsigned *ret) { unsigned arg_cnt = 0; @@ -547,6 +581,7 @@ static HRESULT compile_member_call_expression(compile_ctx_t *ctx, member_express static HRESULT compile_member_expression(compile_ctx_t *ctx, member_expression_t *expr) { expression_t *const_expr; + unsigned prop_ref; int local_ref; if (expr->obj_expr) /* FIXME: we should probably have a dedicated opcode as well */ @@ -555,6 +590,9 @@ static HRESULT compile_member_expression(compile_ctx_t *ctx, member_expression_t if(bind_local(ctx, expr->identifier, &local_ref)) return push_instr_int(ctx, OP_local, local_ref); + if(bind_class_prop(ctx, expr->identifier, &prop_ref)) + return push_instr_uint(ctx, OP_local_prop, prop_ref); + const_expr = lookup_const_decls(ctx, expr->identifier, TRUE); if(const_expr) return compile_expression(ctx, const_expr); @@ -1233,6 +1271,7 @@ static HRESULT compile_assignment(compile_ctx_t *ctx, expression_t *left, expres if(!member_expr->obj_expr) { int local_ref; + unsigned prop_ref; if(bind_local(ctx, member_expr->identifier, &local_ref)) { hres = push_instr_int_uint(ctx, is_set ? OP_set_local : OP_assign_local, local_ref, args_cnt); @@ -1244,6 +1283,17 @@ static HRESULT compile_assignment(compile_ctx_t *ctx, expression_t *left, expres return S_OK; } + if(bind_class_prop(ctx, member_expr->identifier, &prop_ref)) { + hres = push_instr_uint_uint(ctx, is_set ? OP_set_local_prop : OP_assign_local_prop, + prop_ref, args_cnt); + if(FAILED(hres)) + return hres; + + if(!emit_catch(ctx, 0)) + return E_OUTOFMEMORY; + + return S_OK; + } } hres = push_instr_bstr_uint(ctx, op, member_expr->identifier, args_cnt); @@ -2023,6 +2073,8 @@ static HRESULT compile_class(compile_ctx_t *ctx, class_decl_t *class_decl) return E_OUTOFMEMORY; memset(class_desc->funcs, 0, class_desc->func_cnt*sizeof(*class_desc->funcs)); + ctx->class_props = class_decl->props; + for(func_decl = class_decl->funcs, i=1; func_decl; func_decl = func_decl->next, i++) { for(func_prop_decl = func_decl; func_prop_decl; func_prop_decl = func_prop_decl->next_prop_func) { if(func_prop_decl->is_default) { @@ -2060,6 +2112,8 @@ static HRESULT compile_class(compile_ctx_t *ctx, class_decl_t *class_decl) return hres; } + ctx->class_props = NULL; + for(prop_decl = class_decl->props; prop_decl; prop_decl = prop_decl->next) class_desc->prop_cnt++; diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 4b56d80d695..7689c685ada 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -894,6 +894,21 @@ static HRESULT interp_local(exec_ctx_t *ctx) return stack_push(ctx, &r); } +static HRESULT interp_local_prop(exec_ctx_t *ctx) +{ + const unsigned arg = ctx->instr->arg1.uint; + VARIANT *v; + VARIANT r; + + v = ctx->vbthis->props + arg; + + TRACE("%s\n", debugstr_variant(v)); + + V_VT(&r) = VT_BYREF|VT_VARIANT; + V_BYREF(&r) = v; + return stack_push(ctx, &r); +} + static HRESULT interp_ident(exec_ctx_t *ctx) { BSTR identifier = ctx->instr->arg1.bstr; @@ -1116,6 +1131,46 @@ static HRESULT interp_set_local(exec_ctx_t *ctx) return S_OK; } +static HRESULT interp_assign_local_prop(exec_ctx_t *ctx) +{ + const unsigned ref = ctx->instr->arg1.uint; + const unsigned arg_cnt = ctx->instr->arg2.uint; + DISPPARAMS dp; + HRESULT hres; + + TRACE("%u\n", ref); + + vbstack_to_dp(ctx, arg_cnt, TRUE, &dp); + hres = assign_local_var(ctx, ctx->vbthis->props + ref, DISPATCH_PROPERTYPUT, &dp); + if(FAILED(hres)) + return hres; + + stack_popn(ctx, arg_cnt+1); + return S_OK; +} + +static HRESULT interp_set_local_prop(exec_ctx_t *ctx) +{ + const unsigned ref = ctx->instr->arg1.uint; + const unsigned arg_cnt = ctx->instr->arg2.uint; + DISPPARAMS dp; + HRESULT hres; + + TRACE("%u %u\n", ref, arg_cnt); + + hres = stack_assume_disp(ctx, arg_cnt, NULL); + if(FAILED(hres)) + return hres; + + vbstack_to_dp(ctx, arg_cnt, TRUE, &dp); + hres = assign_local_var(ctx, ctx->vbthis->props + ref, DISPATCH_PROPERTYPUTREF, &dp); + if(FAILED(hres)) + return hres; + + stack_popn(ctx, arg_cnt + 1); + return S_OK; +} + static HRESULT interp_assign_member(exec_ctx_t *ctx) { BSTR identifier = ctx->instr->arg1.bstr; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 73c1841823d..1bc3f788577 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -240,9 +240,10 @@ typedef enum { #define OP_LIST \ X(add, 1, 0, 0) \ X(and, 1, 0, 0) \ - X(assign_ident, 1, ARG_BSTR, ARG_UINT) \ - X(assign_local, 1, ARG_INT, ARG_UINT) \ - X(assign_member, 1, ARG_BSTR, ARG_UINT) \ + X(assign_ident, 1, ARG_BSTR, ARG_UINT) \ + X(assign_local, 1, ARG_INT, ARG_UINT) \ + X(assign_local_prop, 1, ARG_UINT, ARG_UINT) \ + X(assign_member, 1, ARG_BSTR, ARG_UINT) \ X(bool, 1, ARG_INT, 0) \ X(catch, 1, ARG_ADDR, ARG_UINT) \ X(case, 0, ARG_ADDR, 0) \ @@ -272,10 +273,11 @@ typedef enum { X(incc_local, 1, ARG_INT, 0) \ X(int, 1, ARG_INT, 0) \ X(is, 1, 0, 0) \ - X(local, 1, ARG_INT, 0) \ X(jmp, 0, ARG_ADDR, 0) \ X(jmp_false, 0, ARG_ADDR, 0) \ X(jmp_true, 0, ARG_ADDR, 0) \ + X(local, 1, ARG_INT, 0) \ + X(local_prop, 1, ARG_UINT, 0) \ X(lt, 1, 0, 0) \ X(lteq, 1, 0, 0) \ X(mcall, 1, ARG_BSTR, ARG_UINT) \ @@ -299,6 +301,7 @@ typedef enum { X(retval, 1, 0, 0) \ X(set_ident, 1, ARG_BSTR, ARG_UINT) \ X(set_local, 1, ARG_INT, ARG_UINT) \ + X(set_local_prop, 1, ARG_UINT, ARG_UINT) \ X(set_member, 1, ARG_BSTR, ARG_UINT) \ X(stack, 1, ARG_UINT, 0) \ X(step, 0, ARG_ADDR, ARG_BSTR) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> Extend compile-time identifier binding to global-scope code. Previously, bind_local() unconditionally skipped binding for FUNC_GLOBAL functions, forcing every global variable access through the string-based lookup_identifier() path at runtime. Now, for code compiled via ParseScriptText (skip_collisions=FALSE), global Dim variables are bound to indexed slots at compile time, emitting OP_local/OP_assign_local instead of OP_ident/OP_assign_ident. At execution time, exec_script() builds a var_refs[] indirection table that maps compile-time indices to their ScriptDisp->global_vars[] storage. This is done once per script block (O(vars * global_vars)), after exec_global_code() has already registered all Dim variables. Execute/ExecuteGlobal code (skip_collisions=TRUE) is not affected: it still skips binding in bind_local() because the destination of its Dim variables (ScriptDisp vs caller dynamic_vars) is not known at compile time. --- dlls/vbscript/compile.c | 14 ++++++++- dlls/vbscript/interp.c | 66 ++++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index c969b9a0058..93a869e6af9 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -64,6 +64,13 @@ typedef struct { function_t *func; function_decl_t *func_decls; dim_decl_t *class_props; + + /* Set for Execute/ExecuteGlobal: (1) suppresses compile-time binding of + * global Dim variables in bind_local(), since they may end up in the + * caller's dynamic_vars rather than ScriptDisp->global_vars; (2) makes + * check_script_collisions() non-fatal so re-declaring an existing name + * is silently tolerated instead of raising VBSE_NAME_REDEFINED. */ + BOOL skip_collisions; } compile_ctx_t; static HRESULT compile_expression(compile_ctx_t*,expression_t*); @@ -491,7 +498,11 @@ static BOOL bind_local(compile_ctx_t *ctx, const WCHAR *name, int *ret) dim_decl_t *dim_decl; unsigned i; - if(ctx->func->type == FUNC_GLOBAL) + /* Execute/Eval code may run from either global or local scope. + * When run from local scope, Dim vars are placed in the caller's + * dynamic_vars, not in ScriptDisp->global_vars. We cannot bind + * them at compile time because we don't know which case it will be. */ + if(ctx->func->type == FUNC_GLOBAL && ctx->skip_collisions) return FALSE; for(dim_decl = ctx->dim_decls, i = 0; dim_decl; dim_decl = dim_decl->next, i++) { @@ -2325,6 +2336,7 @@ HRESULT compile_script(script_ctx_t *script, const WCHAR *src, const WCHAR *item } memset(&ctx, 0, sizeof(ctx)); + ctx.skip_collisions = skip_collisions; code = ctx.code = alloc_vbscode(&ctx, src, cookie, start_line); if(!ctx.code) return E_OUTOFMEMORY; diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 7689c685ada..c90d3fb14bc 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -35,6 +35,9 @@ typedef struct _exec_ctx_t { VARIANT *args; VARIANT *vars; + /* For global-scope code: maps compile-time var indices to their + * ScriptDisp->global_vars[] VARIANT storage. NULL for local scope. */ + VARIANT **var_refs; SAFEARRAY **arrays; dynamic_var_t *dynamic_vars; @@ -873,16 +876,20 @@ static HRESULT interp_mcallv(exec_ctx_t *ctx) return do_mcall(ctx, NULL); } +static inline VARIANT *get_local_var(exec_ctx_t *ctx, int ref) +{ + if(ref < 0) + return ctx->args - ref - 1; + return ctx->var_refs ? ctx->var_refs[ref] : ctx->vars + ref; +} + static HRESULT interp_local(exec_ctx_t *ctx) { const int arg = ctx->instr->arg1.lng; VARIANT *v; VARIANT r; - if(arg < 0) - v = ctx->args - arg - 1; - else - v = ctx->vars + arg; + v = get_local_var(ctx, arg); TRACE("%s\n", debugstr_variant(v)); @@ -1093,7 +1100,7 @@ static HRESULT interp_assign_local(exec_ctx_t *ctx) DISPPARAMS dp; HRESULT hres; - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); TRACE("%d\n", ref); @@ -1114,7 +1121,7 @@ static HRESULT interp_set_local(exec_ctx_t *ctx) DISPPARAMS dp; HRESULT hres; - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); TRACE("%d %u\n", ref, arg_cnt); @@ -1772,7 +1779,7 @@ static HRESULT interp_step_local(exec_ctx_t *ctx) TRACE("%d\n", ref); - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); if(V_VT(stack_top(ctx, 0)) == VT_EMPTY && V_VT(stack_top(ctx, 1)) == VT_EMPTY) { WARN("For loop not initialized\n"); @@ -2747,7 +2754,7 @@ static HRESULT interp_incc_local(exec_ctx_t *ctx) TRACE("%d\n", ref); - lvar = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + lvar = get_local_var(ctx, ref); hres = VarAdd(stack_top(ctx, 0), lvar, &v); if(FAILED(hres)) @@ -2816,8 +2823,16 @@ static void release_exec(exec_ctx_t *ctx) } if(ctx->vars) { - for(i=0; i < ctx->func->var_cnt; i++) - VariantClear(ctx->vars+i); + if(ctx->var_refs) { + /* Only clear fallback slots (vars not found in ScriptDisp) */ + for(i=0; i < ctx->func->var_cnt; i++) { + if(ctx->var_refs[i] == ctx->vars + i) + VariantClear(ctx->vars+i); + } + }else { + for(i=0; i < ctx->func->var_cnt; i++) + VariantClear(ctx->vars+i); + } } if(ctx->arrays) { @@ -2831,6 +2846,7 @@ static void release_exec(exec_ctx_t *ctx) heap_pool_free(&ctx->heap); free(ctx->args); free(ctx->vars); + free(ctx->var_refs); free(ctx->stack); } @@ -2944,6 +2960,36 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd exec.vars = NULL; } + /* For global-scope code (ParseScriptText), resolve compile-time variable + * indices to pointers into ScriptDisp->global_vars[]. This allows OP_local + * and OP_assign_local to access global Dim variables by index instead of + * doing string-based lookup_identifier() at runtime. exec_global_code() + * has already registered all Dim variables into global_vars before this. */ + if(func->type == FUNC_GLOBAL && !exec.caller && func->var_cnt) { + ScriptDisp *obj = func->code_ctx->named_item + ? func->code_ctx->named_item->script_obj : ctx->script_obj; + unsigned i, j; + + exec.var_refs = malloc(func->var_cnt * sizeof(VARIANT*)); + if(!exec.var_refs) { + release_exec(&exec); + return E_OUTOFMEMORY; + } + + for(i = 0; i < func->var_cnt; i++) { + exec.var_refs[i] = NULL; + for(j = 0; j < obj->global_vars_cnt; j++) { + if(!vbs_wcsicmp(func->vars[i].name, obj->global_vars[j]->name)) { + exec.var_refs[i] = &obj->global_vars[j]->v; + break; + } + } + /* If not found (e.g. shadowed by host), fall back to local storage */ + if(!exec.var_refs[i]) + exec.var_refs[i] = exec.vars + i; + } + } + exec.stack_size = 16; exec.top = 0; exec.stack = malloc(exec.stack_size * sizeof(VARIANT)); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> When the loop variable in a For-Each statement can be resolved at compile time (local Dim variable or argument), emit OP_enumnext_local with the variable index instead of OP_enumnext with a string identifier. This bypasses the lookup_identifier() string scan on every iteration. --- dlls/vbscript/compile.c | 34 ++++++++++++++++++++++++------- dlls/vbscript/interp.c | 44 ++++++++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 1 + 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 93a869e6af9..0ab944d2718 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -914,9 +914,13 @@ static HRESULT compile_dowhile_statement(compile_ctx_t *ctx, while_statement_t * static HRESULT compile_foreach_statement(compile_ctx_t *ctx, foreach_statement_t *stat) { statement_ctx_t loop_ctx = {1}; - unsigned loop_start; + unsigned loop_start, instr; + int local_ref; + BOOL is_local; HRESULT hres; + is_local = bind_local(ctx, stat->identifier, &local_ref); + /* Preserve a place on the stack in case we throw before having proper enum collection. */ if(!push_instr(ctx, OP_empty)) return E_OUTOFMEMORY; @@ -931,9 +935,17 @@ static HRESULT compile_foreach_statement(compile_ctx_t *ctx, foreach_statement_t if(!(loop_ctx.for_end_label = alloc_label(ctx))) return E_OUTOFMEMORY; - hres = push_instr_uint_bstr(ctx, OP_enumnext, loop_ctx.for_end_label, stat->identifier); - if(FAILED(hres)) - return hres; + if(is_local) { + instr = push_instr(ctx, OP_enumnext_local); + if(!instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, instr)->arg1.uint = loop_ctx.for_end_label; + instr_ptr(ctx, instr)->arg2.lng = local_ref; + }else { + hres = push_instr_uint_bstr(ctx, OP_enumnext, loop_ctx.for_end_label, stat->identifier); + if(FAILED(hres)) + return hres; + } if(!emit_catch(ctx, 1)) return E_OUTOFMEMORY; @@ -945,9 +957,17 @@ static HRESULT compile_foreach_statement(compile_ctx_t *ctx, foreach_statement_t /* We need a separated enumnext here, because we need to jump out of the loop on exception. */ ctx->loc = stat->stat.loc; - hres = push_instr_uint_bstr(ctx, OP_enumnext, loop_ctx.for_end_label, stat->identifier); - if(FAILED(hres)) - return hres; + if(is_local) { + instr = push_instr(ctx, OP_enumnext_local); + if(!instr) + return E_OUTOFMEMORY; + instr_ptr(ctx, instr)->arg1.uint = loop_ctx.for_end_label; + instr_ptr(ctx, instr)->arg2.lng = local_ref; + }else { + hres = push_instr_uint_bstr(ctx, OP_enumnext, loop_ctx.for_end_label, stat->identifier); + if(FAILED(hres)) + return hres; + } hres = push_instr_addr(ctx, OP_jmp, loop_start); if(FAILED(hres)) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index c90d3fb14bc..a2c292f5946 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1918,6 +1918,50 @@ static HRESULT interp_enumnext(exec_ctx_t *ctx) return S_OK; } +static HRESULT interp_enumnext_local(exec_ctx_t *ctx) +{ + const unsigned loop_end = ctx->instr->arg1.uint; + const int ref = ctx->instr->arg2.lng; + VARIANT v; + IEnumVARIANT *iter; + VARIANT *local_var; + BOOL do_continue; + HRESULT hres; + + TRACE("%d\n", ref); + + if(V_VT(stack_top(ctx, 0)) == VT_EMPTY) { + return MAKE_VBSERROR(VBSE_NOT_ENUM); + } + + assert(V_VT(stack_top(ctx, 0)) == VT_UNKNOWN); + iter = (IEnumVARIANT*)V_UNKNOWN(stack_top(ctx, 0)); + + V_VT(&v) = VT_EMPTY; + hres = IEnumVARIANT_Next(iter, 1, &v, NULL); + if(FAILED(hres)) + return hres; + + do_continue = hres == S_OK; + + local_var = get_local_var(ctx, ref); + if(V_VT(local_var) == (VT_VARIANT|VT_BYREF)) + local_var = V_VARIANTREF(local_var); + + hres = assign_value(ctx, local_var, &v, DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF); + VariantClear(&v); + if(FAILED(hres)) + return hres; + + if(do_continue) { + ctx->instr++; + }else { + stack_popn(ctx, 1); + instr_jmp(ctx, loop_end); + } + return S_OK; +} + static HRESULT interp_jmp(exec_ctx_t *ctx) { const unsigned arg = ctx->instr->arg1.uint; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 1bc3f788577..aed2ccaf5e5 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -256,6 +256,7 @@ typedef enum { X(double, 1, ARG_DOUBLE, 0) \ X(empty, 1, 0, 0) \ X(enumnext, 0, ARG_ADDR, ARG_BSTR) \ + X(enumnext_local, 0, ARG_ADDR, ARG_INT) \ X(equal, 1, 0, 0) \ X(hres, 1, ARG_UINT, 0) \ X(errmode, 1, ARG_INT, 0) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
From: Francis De Brabandere <francisdb@gmail.com> When a For-Each loop iterates over a SAFEARRAY, bypass the IEnumVARIANT COM vtable dispatch and copy elements directly from the array data. This eliminates the COM call overhead and one intermediate VariantCopy per iteration. For non-refcounted element types (VT_I2, VT_I4, VT_R8, etc.), also skip VariantCopyInd and VariantClear entirely, replacing them with a direct struct assignment. Non-SAFEARRAY iterators (e.g. IDispatch collections) continue to use the standard IEnumVARIANT_Next path. For-Each over a 10M-element integer array: 6,339ms -> 58ms (109x faster). --- dlls/vbscript/interp.c | 38 +++++++++++++++++++------------ dlls/vbscript/utils.c | 49 ++++++++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 1 + 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index a2c292f5946..e6de5bc31ad 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1899,11 +1899,16 @@ static HRESULT interp_enumnext(exec_ctx_t *ctx) iter = (IEnumVARIANT*)V_UNKNOWN(stack_top(ctx, 0)); V_VT(&v) = VT_EMPTY; - hres = IEnumVARIANT_Next(iter, 1, &v, NULL); - if(FAILED(hres)) + hres = safearray_iter_next(iter, &v, &do_continue); + if(hres == E_UNEXPECTED) { + hres = IEnumVARIANT_Next(iter, 1, &v, NULL); + if(FAILED(hres)) + return hres; + do_continue = hres == S_OK; + }else if(FAILED(hres)) { return hres; + } - do_continue = hres == S_OK; hres = assign_ident(ctx, ident, DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF, &dp); VariantClear(&v); if(FAILED(hres)) @@ -1922,7 +1927,6 @@ static HRESULT interp_enumnext_local(exec_ctx_t *ctx) { const unsigned loop_end = ctx->instr->arg1.uint; const int ref = ctx->instr->arg2.lng; - VARIANT v; IEnumVARIANT *iter; VARIANT *local_var; BOOL do_continue; @@ -1937,21 +1941,27 @@ static HRESULT interp_enumnext_local(exec_ctx_t *ctx) assert(V_VT(stack_top(ctx, 0)) == VT_UNKNOWN); iter = (IEnumVARIANT*)V_UNKNOWN(stack_top(ctx, 0)); - V_VT(&v) = VT_EMPTY; - hres = IEnumVARIANT_Next(iter, 1, &v, NULL); - if(FAILED(hres)) - return hres; - - do_continue = hres == S_OK; - local_var = get_local_var(ctx, ref); if(V_VT(local_var) == (VT_VARIANT|VT_BYREF)) local_var = V_VARIANTREF(local_var); - hres = assign_value(ctx, local_var, &v, DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF); - VariantClear(&v); - if(FAILED(hres)) + hres = safearray_iter_next(iter, local_var, &do_continue); + if(hres == E_UNEXPECTED) { + VARIANT v; + + V_VT(&v) = VT_EMPTY; + hres = IEnumVARIANT_Next(iter, 1, &v, NULL); + if(FAILED(hres)) + return hres; + + do_continue = hres == S_OK; + hres = assign_value(ctx, local_var, &v, DISPATCH_PROPERTYPUT|DISPATCH_PROPERTYPUTREF); + VariantClear(&v); + if(FAILED(hres)) + return hres; + }else if(FAILED(hres)) { return hres; + } if(do_continue) { ctx->instr++; diff --git a/dlls/vbscript/utils.c b/dlls/vbscript/utils.c index 4bfc2c65a0a..e73495ef3f9 100644 --- a/dlls/vbscript/utils.c +++ b/dlls/vbscript/utils.c @@ -196,3 +196,52 @@ HRESULT create_safearray_iter(SAFEARRAY *sa, BOOL owned, IEnumVARIANT **ev) *ev = &iter->IEnumVARIANT_iface; return S_OK; } + +HRESULT safearray_iter_next(IEnumVARIANT *iface, VARIANT *dst, BOOL *fetched) +{ + safearray_iter *iter; + VARIANT *src; + + if(iface->lpVtbl != &safearray_iter_EnumVARIANTVtbl) + return E_UNEXPECTED; + + iter = impl_from_IEnumVARIANT(iface); + + if(iter->i >= iter->size) { + VariantClear(dst); + *fetched = FALSE; + return S_OK; + } + + src = (VARIANT*)(((BYTE*)iter->sa->pvData) + iter->i * iter->sa->cbElements); + + /* Fast path for non-refcounted types (VT_I2, VT_I4, VT_R8, VT_BOOL, etc.). + * SAFEARRAY elements are stored by value (never VT_BYREF), so VariantCopyInd + * reduces to VariantCopy, which for simple types is just a struct assignment. */ + if(!(V_VT(src) & ~VT_TYPEMASK) && V_VT(src) != VT_BSTR + && V_VT(src) != VT_DISPATCH && V_VT(src) != VT_UNKNOWN) + { + if(V_VT(dst) == VT_BSTR || V_VT(dst) == VT_DISPATCH + || V_VT(dst) == VT_UNKNOWN || (V_VT(dst) & VT_ARRAY)) + VariantClear(dst); + *dst = *src; + } + else + { + VARIANT value; + HRESULT hres; + + V_VT(&value) = VT_EMPTY; + hres = VariantCopyInd(&value, src); + if(FAILED(hres)) + return hres; + + VariantClear(dst); + *dst = value; + } + + iter->i++; + *fetched = TRUE; + return S_OK; +} + diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index aed2ccaf5e5..b70d5f23324 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -455,6 +455,7 @@ BSTR string_replace(BSTR,BSTR,BSTR,int,int,int); void map_vbs_exception(EXCEPINFO *); HRESULT create_safearray_iter(SAFEARRAY *sa, BOOL owned, IEnumVARIANT **ev); +HRESULT safearray_iter_next(IEnumVARIANT *iface, VARIANT *dst, BOOL *fetched); #define FACILITY_VBS 0xa #define MAKE_VBSERROR(code) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_VBS, code) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)