[PATCH v22 0/6] MR10515: Draft: 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. -- v22: vbscript: Bind class properties at compile time. vbscript: Bind For-loop variables at compile time. vbscript: Factor out do_for_step and do_incc from interp_step and interp_incc. 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 16e661c98ff..e084d9531c0 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -480,6 +480,34 @@ static BOOL lookup_func_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; @@ -530,15 +558,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 a71901a1d83..791c5df296c 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -871,6 +871,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 e9eae6b5819..bace0b691cc 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -279,6 +279,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 791c5df296c..3a6707e4ac0 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -940,61 +940,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 e084d9531c0..31e7ed39864 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; @@ -1223,6 +1236,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 3a6707e4ac0..07a95977580 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1068,6 +1068,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 bace0b691cc..b980cf6b9a6 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -250,6 +250,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) \ @@ -305,6 +306,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> Extract the common bodies so that future compile-time-bound For-loop operations on local variables can reuse them instead of duplicating the logic. No functional change. --- dlls/vbscript/interp.c | 84 ++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 07a95977580..31790c584aa 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1642,11 +1642,42 @@ static HRESULT interp_erase(exec_ctx_t *ctx) return hres; } -static HRESULT interp_step(exec_ctx_t *ctx) +static HRESULT do_for_step(exec_ctx_t *ctx, VARIANT *loop_var) { - const BSTR ident = ctx->instr->arg2.bstr; BOOL gteq_zero; VARIANT zero; + HRESULT hres; + + 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(loop_var, 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_step(exec_ctx_t *ctx) +{ + const BSTR ident = ctx->instr->arg2.bstr; ref_t ref; HRESULT hres; @@ -1665,14 +1696,6 @@ static HRESULT interp_step(exec_ctx_t *ctx) 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 = lookup_identifier(ctx, ident, VBDISP_ANY, &ref); if(FAILED(hres)) return hres; @@ -1682,23 +1705,7 @@ static HRESULT interp_step(exec_ctx_t *ctx) return E_FAIL; } - hres = VarCmp(ref.u.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; + return do_for_step(ctx, ref.u.v); } static HRESULT interp_newenum(exec_ctx_t *ctx) @@ -2602,10 +2609,23 @@ static HRESULT interp_neg(exec_ctx_t *ctx) return stack_push(ctx, &v); } +static HRESULT do_incc(exec_ctx_t *ctx, VARIANT *v) +{ + VARIANT tmp; + HRESULT hres; + + hres = VarAdd(stack_top(ctx, 0), v, &tmp); + if(FAILED(hres)) + return hres; + + VariantClear(v); + *v = tmp; + return S_OK; +} + static HRESULT interp_incc(exec_ctx_t *ctx) { const BSTR ident = ctx->instr->arg1.bstr; - VARIANT v; ref_t ref; HRESULT hres; @@ -2620,13 +2640,7 @@ static HRESULT interp_incc(exec_ctx_t *ctx) return E_FAIL; } - hres = VarAdd(stack_top(ctx, 0), ref.u.v, &v); - if(FAILED(hres)) - return hres; - - VariantClear(ref.u.v); - *ref.u.v = v; - return S_OK; + return do_incc(ctx, ref.u.v); } static HRESULT interp_with(exec_ctx_t *ctx) -- 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 | 31 +++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 2 ++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 31e7ed39864..90fa746a600 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -941,8 +941,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; @@ -1011,10 +1015,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)) @@ -1026,11 +1037,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 31790c584aa..d217e43041a 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1708,6 +1708,28 @@ static HRESULT interp_step(exec_ctx_t *ctx) return do_for_step(ctx, ref.u.v); } +static HRESULT interp_step_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg2.lng; + VARIANT *v; + + 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; + } + + return do_for_step(ctx, v); +} + static HRESULT interp_newenum(exec_ctx_t *ctx) { variant_val_t v; @@ -2643,6 +2665,15 @@ static HRESULT interp_incc(exec_ctx_t *ctx) return do_incc(ctx, ref.u.v); } +static HRESULT interp_incc_local(exec_ctx_t *ctx) +{ + const int ref = ctx->instr->arg1.lng; + + TRACE("%d\n", ref); + + return do_incc(ctx, ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref); +} + static HRESULT interp_with(exec_ctx_t *ctx) { VARIANT *v; diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index b980cf6b9a6..00e73677b67 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -278,6 +278,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) \ @@ -310,6 +311,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 90fa746a600..b7f4d242d35 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; @@ -521,6 +535,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; @@ -571,6 +605,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 */ @@ -579,6 +614,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); @@ -1257,6 +1295,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); @@ -1268,6 +1307,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); @@ -2075,6 +2125,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) { @@ -2112,6 +2164,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 d217e43041a..1de9cc9ee9f 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -892,6 +892,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; @@ -1114,6 +1129,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 00e73677b67..1d441030365 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -249,9 +249,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) \ @@ -281,10 +282,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) \ @@ -308,6 +310,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
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)