# HG changeset patch # User Bram Moolenaar # Date 1631802606 -7200 # Node ID dd4e86558836265dff2149020dbc9d8e022ef1d8 # Parent 2577687dbdcf06c9483a0ac7ea4b50d321175457 patch 8.2.3442: Vim9: || and && are not handled at compile time Commit: https://github.com/vim/vim/commit/1a7ee4dd115329052670d7af176341bd09c9dc5a Author: Bram Moolenaar Date: Thu Sep 16 16:15:07 2021 +0200 patch 8.2.3442: Vim9: || and && are not handled at compile time Problem: Vim9: || and && are not handled at compile time when possible. Solution: When using constants generate fewer instructions. diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -1218,6 +1218,38 @@ def Test_disassemble_and_or() instr) enddef +def AndConstant(arg: any): string + if true && arg + return "yes" + endif + if false && arg + return "never" + endif + return "no" +enddef + +def Test_disassemble_and_constant() + assert_equal("yes", AndConstant(1)) + assert_equal("no", AndConstant(false)) + var instr = execute('disassemble AndConstant') + assert_match('AndConstant\_s*' .. + 'if true && arg\_s*' .. + '0 LOAD arg\[-1\]\_s*' .. + '1 COND2BOOL\_s*' .. + '2 JUMP_IF_FALSE -> 5\_s*' .. + 'return "yes"\_s*' .. + '3 PUSHS "yes"\_s*' .. + '4 RETURN\_s*' .. + 'endif\_s*' .. + 'if false && arg\_s*' .. + 'return "never"\_s*' .. + 'endif\_s*' .. + 'return "no"\_s*' .. + '5 PUSHS "no"\_s*' .. + '6 RETURN', + instr) +enddef + def ForLoop(): list var res: list for i in range(3) @@ -1734,25 +1766,31 @@ def Test_disassemble_invert_bool() enddef def ReturnBool(): bool - var name: bool = 1 && 0 || 1 + var one = 1 + var zero = 0 + var name: bool = one && zero || one return name enddef def Test_disassemble_return_bool() var instr = execute('disassemble ReturnBool') assert_match('ReturnBool\_s*' .. - 'var name: bool = 1 && 0 || 1\_s*' .. - '0 PUSHNR 1\_s*' .. - '1 COND2BOOL\_s*' .. - '2 JUMP_IF_COND_FALSE -> 5\_s*' .. - '3 PUSHNR 0\_s*' .. - '4 COND2BOOL\_s*' .. - '5 JUMP_IF_COND_TRUE -> 8\_s*' .. - '6 PUSHNR 1\_s*' .. - '7 COND2BOOL\_s*' .. - '\d STORE $0\_s*' .. + 'var one = 1\_s*' .. + '0 STORE 1 in $0\_s*' .. + 'var zero = 0\_s*' .. + '1 STORE 0 in $1\_s*' .. + 'var name: bool = one && zero || one\_s*' .. + '2 LOAD $0\_s*' .. + '3 COND2BOOL\_s*' .. + '4 JUMP_IF_COND_FALSE -> 7\_s*' .. + '5 LOAD $1\_s*' .. + '6 COND2BOOL\_s*' .. + '7 JUMP_IF_COND_TRUE -> 10\_s*' .. + '8 LOAD $0\_s*' .. + '9 COND2BOOL\_s*' .. + '10 STORE $2\_s*' .. 'return name\_s*' .. - '\d\+ LOAD $0\_s*' .. + '\d\+ LOAD $2\_s*' .. '\d\+ RETURN', instr) assert_equal(true, InvertBool()) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3442, +/**/ 3441, /**/ 3440, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -221,6 +221,7 @@ typedef struct { typedef enum { JUMP_ALWAYS, + JUMP_NEVER, JUMP_IF_FALSE, // pop and jump if false JUMP_AND_KEEP_IF_TRUE, // jump if top of stack is truthy, drop if not JUMP_AND_KEEP_IF_FALSE, // jump if top of stack is falsy, drop if not diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -2847,7 +2847,7 @@ generate_ppconst(cctx_T *cctx, ppconst_T } /* - * Check that the last item of "ppconst" is a bool. + * Check that the last item of "ppconst" is a bool, if there is an item. */ static int check_ppconst_bool(ppconst_T *ppconst) @@ -4845,7 +4845,8 @@ compile_expr7( } else { - if (generate_ppconst(cctx, ppconst) == FAIL) + if (cctx->ctx_skip != SKIP_YES + && generate_ppconst(cctx, ppconst) == FAIL) return FAIL; r = compile_load(arg, p, cctx, TRUE, TRUE); } @@ -5240,6 +5241,7 @@ compile_and_or( { garray_T *instr = &cctx->ctx_instr; garray_T end_ga; + int save_skip = cctx->ctx_skip; /* * Repeat until there is no following "||" or "&&" @@ -5251,7 +5253,10 @@ compile_and_or( long save_sourcing_lnum; int start_ctx_lnum = cctx->ctx_lnum; int save_lnum; + int const_used; int status; + jumpwhen_T jump_when = opchar == '|' + ? JUMP_IF_COND_TRUE : JUMP_IF_COND_FALSE; if (next != NULL) { @@ -5274,14 +5279,38 @@ compile_and_or( status = check_ppconst_bool(ppconst); if (status != FAIL) { - // TODO: use ppconst if the value is a constant - generate_ppconst(cctx, ppconst); - - // Every part must evaluate to a bool. - status = bool_on_stack(cctx); - if (status != FAIL) - status = ga_grow(&end_ga, 1); - } + // Use the last ppconst if possible. + if (ppconst->pp_used > 0) + { + typval_T *tv = &ppconst->pp_tv[ppconst->pp_used - 1]; + int is_true = tv2bool(tv); + + if ((is_true && opchar == '|') + || (!is_true && opchar == '&')) + { + // For "false && expr" and "true || expr" the "expr" + // does not need to be evaluated. + cctx->ctx_skip = SKIP_YES; + clear_tv(tv); + tv->v_type = VAR_BOOL; + tv->vval.v_number = is_true ? VVAL_TRUE : VVAL_FALSE; + } + else + { + // For "true && expr" and "false || expr" only "expr" + // needs to be evaluated. + --ppconst->pp_used; + jump_when = JUMP_NEVER; + } + } + else + { + // Every part must evaluate to a bool. + status = bool_on_stack(cctx); + } + } + if (status != FAIL) + status = ga_grow(&end_ga, 1); cctx->ctx_lnum = save_lnum; if (status == FAIL) { @@ -5289,10 +5318,15 @@ compile_and_or( return FAIL; } - *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; - ++end_ga.ga_len; - generate_JUMP(cctx, opchar == '|' - ? JUMP_IF_COND_TRUE : JUMP_IF_COND_FALSE, 0); + if (jump_when != JUMP_NEVER) + { + if (cctx->ctx_skip != SKIP_YES) + { + *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; + ++end_ga.ga_len; + } + generate_JUMP(cctx, jump_when, 0); + } // eval the next expression SOURCING_LNUM = save_sourcing_lnum; @@ -5302,6 +5336,7 @@ compile_and_or( return FAIL; } + const_used = ppconst->pp_used; if ((opchar == '|' ? compile_expr3(arg, cctx, ppconst) : compile_expr4(arg, cctx, ppconst)) == FAIL) { @@ -5309,6 +5344,20 @@ compile_and_or( return FAIL; } + // "0 || 1" results in true, "1 && 0" results in false. + if (ppconst->pp_used == const_used + 1) + { + typval_T *tv = &ppconst->pp_tv[ppconst->pp_used - 1]; + + if (tv->v_type == VAR_NUMBER + && (tv->vval.v_number == 1 || tv->vval.v_number == 0)) + { + tv->vval.v_number = tv->vval.v_number == 1 + ? VVAL_TRUE : VVAL_FALSE; + tv->v_type = VAR_BOOL; + } + } + p = may_peek_next_line(cctx, *arg, &next); } @@ -5317,26 +5366,32 @@ compile_and_or( ga_clear(&end_ga); return FAIL; } - generate_ppconst(cctx, ppconst); - - // Every part must evaluate to a bool. - if (bool_on_stack(cctx) == FAIL) - { + + if (cctx->ctx_skip != SKIP_YES && ppconst->pp_used == 0) + // Every part must evaluate to a bool. + if (bool_on_stack(cctx) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + + if (end_ga.ga_len > 0) + { + // Fill in the end label in all jumps. + generate_ppconst(cctx, ppconst); + while (end_ga.ga_len > 0) + { + isn_T *isn; + + --end_ga.ga_len; + isn = ((isn_T *)instr->ga_data) + + *(((int *)end_ga.ga_data) + end_ga.ga_len); + isn->isn_arg.jump.jump_where = instr->ga_len; + } ga_clear(&end_ga); - return FAIL; - } - - // Fill in the end label in all jumps. - while (end_ga.ga_len > 0) - { - isn_T *isn; - - --end_ga.ga_len; - isn = ((isn_T *)instr->ga_data) - + *(((int *)end_ga.ga_data) + end_ga.ga_len); - isn->isn_arg.jump.jump_where = instr->ga_len; - } - ga_clear(&end_ga); + } + + cctx->ctx_skip = save_skip; } return OK; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -5487,6 +5487,9 @@ list_instructions(char *pfx, isn_T *inst case JUMP_ALWAYS: when = "JUMP"; break; + case JUMP_NEVER: + iemsg("JUMP_NEVER should not be used"); + break; case JUMP_AND_KEEP_IF_TRUE: when = "JUMP_AND_KEEP_IF_TRUE"; break;