To: vim_dev@googlegroups.com Subject: Patch 8.2.0717 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0717 Problem: Vim9: postponed constant expressions does not scale. Solution: Add a structure to pass around postponed constants. Files: sr/vim9compile.c, src/testdir/test_vim9_disassemble.vim *** ../vim-8.2.0716/src/vim9compile.c 2020-05-07 23:20:17.607439483 +0200 --- src/vim9compile.c 2020-05-08 19:02:40.415646972 +0200 *************** *** 1041,1091 **** } /* - * Generate a PUSH instruction for "tv". - * "tv" will be consumed or cleared. "tv" may be NULL; - */ - static int - generate_tv_PUSH(cctx_T *cctx, typval_T *tv) - { - if (tv != NULL) - { - switch (tv->v_type) - { - case VAR_UNKNOWN: - break; - case VAR_BOOL: - generate_PUSHBOOL(cctx, tv->vval.v_number); - break; - case VAR_SPECIAL: - generate_PUSHSPEC(cctx, tv->vval.v_number); - break; - case VAR_NUMBER: - generate_PUSHNR(cctx, tv->vval.v_number); - break; - #ifdef FEAT_FLOAT - case VAR_FLOAT: - generate_PUSHF(cctx, tv->vval.v_float); - break; - #endif - case VAR_BLOB: - generate_PUSHBLOB(cctx, tv->vval.v_blob); - tv->vval.v_blob = NULL; - break; - case VAR_STRING: - generate_PUSHS(cctx, tv->vval.v_string); - tv->vval.v_string = NULL; - break; - default: - iemsg("constant type not supported"); - clear_tv(tv); - return FAIL; - } - tv->v_type = VAR_UNKNOWN; - } - return OK; - } - - /* * Generate an ISN_STORE instruction. */ static int --- 1041,1046 ---- *************** *** 3671,3676 **** --- 3626,3716 ---- return OK; } + // Structure passed between the compile_expr* functions to keep track of + // constants that have been parsed but for which no code was produced yet. If + // possible expressions on these constants are applied at compile time. If + // that is not possible, the code to push the constants needs to be generated + // before other instructions. + typedef struct { + typval_T pp_tv[10]; // stack of ppconst constants + int pp_used; // active entries in pp_tv[] + } ppconst_T; + + /* + * Generate a PUSH instruction for "tv". + * "tv" will be consumed or cleared. + * Nothing happens if "tv" is NULL or of type VAR_UNKNOWN; + */ + static int + generate_tv_PUSH(cctx_T *cctx, typval_T *tv) + { + if (tv != NULL) + { + switch (tv->v_type) + { + case VAR_UNKNOWN: + break; + case VAR_BOOL: + generate_PUSHBOOL(cctx, tv->vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, tv->vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, tv->vval.v_number); + break; + #ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, tv->vval.v_float); + break; + #endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, tv->vval.v_blob); + tv->vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, tv->vval.v_string); + tv->vval.v_string = NULL; + break; + default: + iemsg("constant type not supported"); + clear_tv(tv); + return FAIL; + } + tv->v_type = VAR_UNKNOWN; + } + return OK; + } + + /* + * Generate code for any ppconst entries. + */ + static int + generate_ppconst(cctx_T *cctx, ppconst_T *ppconst) + { + int i; + int ret = OK; + + for (i = 0; i < ppconst->pp_used; ++i) + if (generate_tv_PUSH(cctx, &ppconst->pp_tv[i]) == FAIL) + ret = FAIL; + ppconst->pp_used = 0; + return ret; + } + + /* + * Clear ppconst constants. Used when failing. + */ + static void + clear_ppconst(ppconst_T *ppconst) + { + int i; + + for (i = 0; i < ppconst->pp_used; ++i) + clear_tv(&ppconst->pp_tv[i]); + ppconst->pp_used = 0; + } + /* * Compile code to apply '-', '+' and '!'. */ *************** *** 3728,3736 **** cctx_T *cctx, char_u **start_leader, char_u *end_leader, ! typval_T *bef1_tv, ! typval_T *bef2_tv, ! typval_T *new_tv) { for (;;) { --- 3768,3774 ---- cctx_T *cctx, char_u **start_leader, char_u *end_leader, ! ppconst_T *ppconst) { for (;;) { *************** *** 3740,3748 **** type_T *type; int argcount = 0; ! if (generate_tv_PUSH(cctx, bef1_tv) == FAIL ! || generate_tv_PUSH(cctx, bef2_tv) == FAIL ! || generate_tv_PUSH(cctx, new_tv) == FAIL) return FAIL; // funcref(arg) --- 3778,3784 ---- type_T *type; int argcount = 0; ! if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; // funcref(arg) *************** *** 3758,3766 **** { char_u *p; ! if (generate_tv_PUSH(cctx, bef1_tv) == FAIL ! || generate_tv_PUSH(cctx, bef2_tv) == FAIL ! || generate_tv_PUSH(cctx, new_tv) == FAIL) return FAIL; // something->method() --- 3794,3800 ---- { char_u *p; ! if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; // something->method() *************** *** 3800,3808 **** garray_T *stack; type_T **typep; ! if (generate_tv_PUSH(cctx, bef1_tv) == FAIL ! || generate_tv_PUSH(cctx, bef2_tv) == FAIL ! || generate_tv_PUSH(cctx, new_tv) == FAIL) return FAIL; // list index: list[123] --- 3834,3840 ---- garray_T *stack; type_T **typep; ! if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; // list index: list[123] *************** *** 3835,3843 **** { char_u *p; ! if (generate_tv_PUSH(cctx, bef1_tv) == FAIL ! || generate_tv_PUSH(cctx, bef2_tv) == FAIL ! || generate_tv_PUSH(cctx, new_tv) == FAIL) return FAIL; ++*arg; --- 3867,3873 ---- { char_u *p; ! if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; ++*arg; *************** *** 3871,3878 **** * Compile an expression at "*arg" and add instructions to "cctx->ctx_instr". * "arg" is advanced until after the expression, skipping white space. * ! * If the value is a constant "new_tv" will be set. ! * Before instructions are generated, any "bef_tv" will generated. * * This is the compiling equivalent of eval1(), eval2(), etc. */ --- 3901,3908 ---- * Compile an expression at "*arg" and add instructions to "cctx->ctx_instr". * "arg" is advanced until after the expression, skipping white space. * ! * If the value is a constant "ppconst->pp_ret" will be set. ! * Before instructions are generated, any values in "ppconst" will generated. * * This is the compiling equivalent of eval1(), eval2(), etc. */ *************** *** 3905,3917 **** compile_expr7( char_u **arg, cctx_T *cctx, ! typval_T *bef1_tv, ! typval_T *bef2_tv, ! typval_T *new_tv) { - typval_T rettv; char_u *start_leader, *end_leader; int ret = OK; /* * Skip '!', '-' and '+' characters. They are handled later. --- 3935,3945 ---- compile_expr7( char_u **arg, cctx_T *cctx, ! ppconst_T *ppconst) { char_u *start_leader, *end_leader; int ret = OK; + typval_T *rettv = &ppconst->pp_tv[ppconst->pp_used]; /* * Skip '!', '-' and '+' characters. They are handled later. *************** *** 3921,3927 **** *arg = skipwhite(*arg + 1); end_leader = *arg; ! rettv.v_type = VAR_UNKNOWN; switch (**arg) { /* --- 3949,3955 ---- *arg = skipwhite(*arg + 1); end_leader = *arg; ! rettv->v_type = VAR_UNKNOWN; switch (**arg) { /* *************** *** 3937,3964 **** case '7': case '8': case '9': ! case '.': if (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL) return FAIL; break; /* * String constant: "string". */ ! case '"': if (get_string_tv(arg, &rettv, TRUE) == FAIL) return FAIL; break; /* * Literal string constant: 'str''ing'. */ ! case '\'': if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL) return FAIL; break; /* * Constant Vim variable. */ ! case 'v': get_vim_constant(arg, &rettv); ret = NOTDONE; break; --- 3965,3992 ---- case '7': case '8': case '9': ! case '.': if (get_number_tv(arg, rettv, TRUE, FALSE) == FAIL) return FAIL; break; /* * String constant: "string". */ ! case '"': if (get_string_tv(arg, rettv, TRUE) == FAIL) return FAIL; break; /* * Literal string constant: 'str''ing'. */ ! case '\'': if (get_lit_string_tv(arg, rettv, TRUE) == FAIL) return FAIL; break; /* * Constant Vim variable. */ ! case 'v': get_vim_constant(arg, rettv); ret = NOTDONE; break; *************** *** 4036,4061 **** if (ret == FAIL) return FAIL; ! if (rettv.v_type != VAR_UNKNOWN) { // apply the '!', '-' and '+' before the constant ! if (apply_leader(&rettv, start_leader, end_leader) == FAIL) { ! clear_tv(&rettv); return FAIL; } start_leader = end_leader; // don't apply again below ! // A constant expression can possibly be handled compile time. ! *new_tv = rettv; } else if (ret == NOTDONE) { char_u *p; int r; ! if (generate_tv_PUSH(cctx, bef1_tv) == FAIL ! || generate_tv_PUSH(cctx, bef2_tv) == FAIL) return FAIL; if (!eval_isnamec1(**arg)) --- 4064,4089 ---- if (ret == FAIL) return FAIL; ! if (rettv->v_type != VAR_UNKNOWN) { // apply the '!', '-' and '+' before the constant ! if (apply_leader(rettv, start_leader, end_leader) == FAIL) { ! clear_tv(rettv); return FAIL; } start_leader = end_leader; // don't apply again below ! // A constant expression can possibly be handled compile time, return ! // the value instead of generating code. ! ++ppconst->pp_used; } else if (ret == NOTDONE) { char_u *p; int r; ! if (generate_ppconst(cctx, ppconst) == FAIL) return FAIL; if (!eval_isnamec1(**arg)) *************** *** 4077,4088 **** // Handle following "[]", ".member", etc. // Then deal with prefixed '-', '+' and '!', if not done already. if (compile_subscript(arg, cctx, &start_leader, end_leader, ! bef1_tv, bef2_tv, new_tv) == FAIL || compile_leader(cctx, start_leader, end_leader) == FAIL) - { - clear_tv(new_tv); return FAIL; - } return OK; } --- 4105,4113 ---- // Handle following "[]", ".member", etc. // Then deal with prefixed '-', '+' and '!', if not done already. if (compile_subscript(arg, cctx, &start_leader, end_leader, ! ppconst) == FAIL || compile_leader(cctx, start_leader, end_leader) == FAIL) return FAIL; return OK; } *************** *** 4093,4107 **** */ static int compile_expr6( ! char_u **arg, ! cctx_T *cctx, ! typval_T *bef_tv, ! typval_T *new_tv) { char_u *op; // get the first expression ! if (compile_expr7(arg, cctx, NULL, bef_tv, new_tv) == FAIL) return FAIL; /* --- 4118,4132 ---- */ static int compile_expr6( ! char_u **arg, ! cctx_T *cctx, ! ppconst_T *ppconst) { char_u *op; + int ppconst_used = ppconst->pp_used; // get the first expression ! if (compile_expr7(arg, cctx, ppconst) == FAIL) return FAIL; /* *************** *** 4109,4116 **** */ for (;;) { - typval_T tv2; - op = skipwhite(*arg); if (*op != '*' && *op != '/' && *op != '%') break; --- 4134,4139 ---- *************** *** 4128,4156 **** return FAIL; // get the second expression ! tv2.v_type = VAR_UNKNOWN; ! if (compile_expr7(arg, cctx, bef_tv, new_tv, &tv2) == FAIL) return FAIL; ! if (new_tv->v_type == VAR_NUMBER && tv2.v_type == VAR_NUMBER) { varnumber_T res = 0; // both are numbers: compute the result switch (*op) { ! case '*': res = new_tv->vval.v_number * tv2.vval.v_number; break; ! case '/': res = new_tv->vval.v_number / tv2.vval.v_number; break; ! case '%': res = new_tv->vval.v_number % tv2.vval.v_number; break; } ! new_tv->vval.v_number = res; } else { ! generate_tv_PUSH(cctx, new_tv); ! generate_tv_PUSH(cctx, &tv2); generate_two_op(cctx, op); } } --- 4151,4183 ---- return FAIL; // get the second expression ! if (compile_expr7(arg, cctx, ppconst) == FAIL) return FAIL; ! ! if (ppconst->pp_used == ppconst_used + 2 ! && ppconst->pp_tv[ppconst_used].v_type == VAR_NUMBER ! && ppconst->pp_tv[ppconst_used + 1].v_type == VAR_NUMBER) { + typval_T *tv1 = &ppconst->pp_tv[ppconst_used]; + typval_T *tv2 = &ppconst->pp_tv[ppconst_used + 1]; varnumber_T res = 0; // both are numbers: compute the result switch (*op) { ! case '*': res = tv1->vval.v_number * tv2->vval.v_number; break; ! case '/': res = tv1->vval.v_number / tv2->vval.v_number; break; ! case '%': res = tv1->vval.v_number % tv2->vval.v_number; break; } ! tv1->vval.v_number = res; ! --ppconst->pp_used; } else { ! generate_ppconst(cctx, ppconst); generate_two_op(cctx, op); } } *************** *** 4166,4187 **** static int compile_expr5(char_u **arg, cctx_T *cctx) { - typval_T tv1; char_u *op; int oplen; // get the first variable ! tv1.v_type = VAR_UNKNOWN; ! if (compile_expr6(arg, cctx, NULL, &tv1) == FAIL) return FAIL; /* * Repeat computing, until no "+", "-" or ".." is following. */ for (;;) { - typval_T tv2; - op = skipwhite(*arg); if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) break; --- 4193,4217 ---- static int compile_expr5(char_u **arg, cctx_T *cctx) { char_u *op; int oplen; + ppconst_T ppconst; + int ppconst_used = 0; + + CLEAR_FIELD(ppconst); // get the first variable ! if (compile_expr6(arg, cctx, &ppconst) == FAIL) ! { ! clear_ppconst(&ppconst); return FAIL; + } /* * Repeat computing, until no "+", "-" or ".." is following. */ for (;;) { op = skipwhite(*arg); if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) break; *************** *** 4191,4197 **** { char_u buf[3]; ! clear_tv(&tv1); vim_strncpy(buf, op, oplen); semsg(_(e_white_both), buf); return FAIL; --- 4221,4227 ---- { char_u buf[3]; ! clear_ppconst(&ppconst); vim_strncpy(buf, op, oplen); semsg(_(e_white_both), buf); return FAIL; *************** *** 4200,4257 **** *arg = skipwhite(op + oplen); if (may_get_next_line(op + oplen, arg, cctx) == FAIL) { ! clear_tv(&tv1); return FAIL; } // get the second expression ! tv2.v_type = VAR_UNKNOWN; ! if (compile_expr6(arg, cctx, &tv1, &tv2) == FAIL) { ! clear_tv(&tv1); return FAIL; } ! if (*op == '+' && tv1.v_type == VAR_NUMBER && tv2.v_type == VAR_NUMBER) ! { ! // add constant numbers ! tv1.vval.v_number = tv1.vval.v_number + tv2.vval.v_number; ! } ! else if (*op == '-' && tv1.v_type == VAR_NUMBER ! && tv2.v_type == VAR_NUMBER) ! { ! // subtract constant numbers ! tv1.vval.v_number = tv1.vval.v_number - tv2.vval.v_number; ! } ! else if (*op == '.' && tv1.v_type == VAR_STRING ! && tv2.v_type == VAR_STRING) { ! // concatenate constant strings ! char_u *s1 = tv1.vval.v_string; ! char_u *s2 = tv2.vval.v_string; ! size_t len1 = STRLEN(s1); ! tv1.vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); ! if (tv1.vval.v_string == NULL) { vim_free(s1); vim_free(s2); - return FAIL; } ! mch_memmove(tv1.vval.v_string, s1, len1); ! STRCPY(tv1.vval.v_string + len1, s2); ! vim_free(s1); ! vim_free(s2); } else { ! generate_tv_PUSH(cctx, &tv1); ! generate_tv_PUSH(cctx, &tv2); if (*op == '.') { if (may_generate_2STRING(-2, cctx) == FAIL || may_generate_2STRING(-1, cctx) == FAIL) return FAIL; generate_instr_drop(cctx, ISN_CONCAT, 1); } else --- 4230,4292 ---- *arg = skipwhite(op + oplen); if (may_get_next_line(op + oplen, arg, cctx) == FAIL) { ! clear_ppconst(&ppconst); return FAIL; } // get the second expression ! if (compile_expr6(arg, cctx, &ppconst) == FAIL) { ! clear_ppconst(&ppconst); return FAIL; } ! if (ppconst.pp_used == ppconst_used + 2 ! && (*op == '.' ! ? (ppconst.pp_tv[ppconst_used].v_type == VAR_STRING ! && ppconst.pp_tv[ppconst_used + 1].v_type == VAR_STRING) ! : (ppconst.pp_tv[ppconst_used].v_type == VAR_NUMBER ! && ppconst.pp_tv[ppconst_used + 1].v_type == VAR_NUMBER))) { ! typval_T *tv1 = &ppconst.pp_tv[ppconst_used]; ! typval_T *tv2 = &ppconst.pp_tv[ppconst_used + 1]; ! // concat/subtract/add constant numbers ! if (*op == '+') ! tv1->vval.v_number = tv1->vval.v_number + tv2->vval.v_number; ! else if (*op == '-') ! tv1->vval.v_number = tv1->vval.v_number - tv2->vval.v_number; ! else { + // concatenate constant strings + char_u *s1 = tv1->vval.v_string; + char_u *s2 = tv2->vval.v_string; + size_t len1 = STRLEN(s1); + + tv1->vval.v_string = alloc((int)(len1 + STRLEN(s2) + 1)); + if (tv1->vval.v_string == NULL) + { + clear_ppconst(&ppconst); + return FAIL; + } + mch_memmove(tv1->vval.v_string, s1, len1); + STRCPY(tv1->vval.v_string + len1, s2); vim_free(s1); vim_free(s2); } ! --ppconst.pp_used; } else { ! generate_ppconst(cctx, &ppconst); if (*op == '.') { if (may_generate_2STRING(-2, cctx) == FAIL || may_generate_2STRING(-1, cctx) == FAIL) + { + clear_ppconst(&ppconst); return FAIL; + } generate_instr_drop(cctx, ISN_CONCAT, 1); } else *************** *** 4260,4266 **** } // TODO: move to caller ! generate_tv_PUSH(cctx, &tv1); return OK; } --- 4295,4301 ---- } // TODO: move to caller ! generate_ppconst(cctx, &ppconst); return OK; } *** ../vim-8.2.0716/src/testdir/test_vim9_disassemble.vim 2020-05-07 16:58:10.868572663 +0200 --- src/testdir/test_vim9_disassemble.vim 2020-05-08 19:05:53.883125772 +0200 *************** *** 1039,1050 **** --- 1039,1060 ---- return 'one' .. 'two' .. 'three' enddef + def s:ComputeConst(): number + return 2 + 3 * 4 / 6 + 7 + enddef + def Test_simplify_const_expr() let res = execute('disass s:ConcatStrings') assert_match('\\d*_ConcatStrings.*' .. '\d PUSHS "onetwothree".*' .. '\d RETURN', res) + + res = execute('disass s:ComputeConst') + assert_match('\\d*_ComputeConst.*' .. + '\d PUSHNR 11.*' .. + '\d RETURN', + res) enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.0716/src/version.c 2020-05-07 23:20:17.611439470 +0200 --- src/version.c 2020-05-08 19:09:11.006567983 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 717, /**/ -- hundred-and-one symptoms of being an internet addict: 72. Somebody at IRC just mentioned a way to obtain full motion video without a PC using a wireless protocol called NTSC, you wonder how you never heard about it /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///