patch 8.2.2400: Vim9: compiled functions are not profiled

Problem:    Vim9: compiled functions are not profiled.
Solution:   Add initial changes to profile compiled functions.  Fix that a
            script-local function was hard to debug.
This commit is contained in:
Bram Moolenaar
2021-01-24 12:53:53 +01:00
parent 7cf0c114d6
commit b204990346
16 changed files with 311 additions and 109 deletions

View File

@ -1,4 +1,4 @@
*repeat.txt* For Vim version 8.2. Last change: 2021 Jan 02 *repeat.txt* For Vim version 8.2. Last change: 2021 Jan 23
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -354,7 +354,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
Vim version, or update Vim to a newer version. See Vim version, or update Vim to a newer version. See
|vimscript-version| for what changed between versions. |vimscript-version| for what changed between versions.
:vim9script [noclear] *:vim9* *:vim9script* :vim9[script] [noclear] *:vim9* *:vim9script*
Marks a script file as containing |Vim9-script| Marks a script file as containing |Vim9-script|
commands. Also see |vim9-namespace|. commands. Also see |vim9-namespace|.
Must be the first command in the file. Must be the first command in the file.
@ -899,8 +899,9 @@ matches ".../plugin/explorer.vim", ".../plugin/iexplorer.vim", etc. >
matches ".../plugin/explorer.vim" and "explorer.vim" in any other directory. matches ".../plugin/explorer.vim" and "explorer.vim" in any other directory.
The match for functions is done against the name as it's shown in the output The match for functions is done against the name as it's shown in the output
of ":function". For local functions this means that something like "<SNR>99_" of ":function". However, for local functions the script-specific prefix such
is prepended. as "<SNR>99_" is ignored to make it easier to match script-local functions
without knowing the ID of the script.
Note that functions are first loaded and later executed. When they are loaded Note that functions are first loaded and later executed. When they are loaded
the "file" breakpoints are checked, when they are executed the "func" the "file" breakpoints are checked, when they are executed the "func"
@ -955,10 +956,10 @@ OBSCURE
Profiling means that Vim measures the time that is spent on executing Profiling means that Vim measures the time that is spent on executing
functions and/or scripts. The |+profile| feature is required for this. functions and/or scripts. The |+profile| feature is required for this.
It is only included when Vim was compiled with "huge" features. It is included when Vim was compiled with "huge" features.
You can also use the |reltime()| function to measure time. This only requires You can also use the |reltime()| function to measure time. This only requires
the |+reltime| feature, which is present more often. the |+reltime| feature, which is present in more builds.
For profiling syntax highlighting see |:syntime|. For profiling syntax highlighting see |:syntime|.
@ -1005,7 +1006,12 @@ For example, to profile the one_script.vim script file: >
You must always start with a ":profile start fname" command. The resulting You must always start with a ":profile start fname" command. The resulting
file is written when Vim exits. Here is an example of the output, with line file is written when Vim exits. For example, to profile one specific
function: >
profile start /tmp/vimprofile
profile func MyFunc
Here is an example of the output, with line
numbers prepended for the explanation: numbers prepended for the explanation:
1 FUNCTION Test2() ~ 1 FUNCTION Test2() ~

View File

@ -864,7 +864,7 @@ has_profiling(
*/ */
static linenr_T static linenr_T
debuggy_find( debuggy_find(
int file, // TRUE for a file, FALSE for a function int is_file, // TRUE for a file, FALSE for a function
char_u *fname, // file or function name char_u *fname, // file or function name
linenr_T after, // after this line number linenr_T after, // after this line number
garray_T *gap, // either &dbg_breakp or &prof_ga garray_T *gap, // either &dbg_breakp or &prof_ga
@ -873,20 +873,25 @@ debuggy_find(
struct debuggy *bp; struct debuggy *bp;
int i; int i;
linenr_T lnum = 0; linenr_T lnum = 0;
char_u *name = fname; char_u *name = NULL;
char_u *short_name = fname;
int prev_got_int; int prev_got_int;
// Return quickly when there are no breakpoints. // Return quickly when there are no breakpoints.
if (gap->ga_len == 0) if (gap->ga_len == 0)
return (linenr_T)0; return (linenr_T)0;
// Replace K_SNR in function name with "<SNR>". // For a script-local function remove the prefix, so that
if (!file && fname[0] == K_SPECIAL) // "profile func Func" matches "Func" in any script. Otherwise it's very
// difficult to profile/debug a script-local function. It may match a
// function in the wrong script, but that is much better than not being
// able to profile/debug a function in a script with unknown ID.
// Also match a script-specific name.
if (!is_file && fname[0] == K_SPECIAL)
{ {
short_name = vim_strchr(fname, '_') + 1;
name = alloc(STRLEN(fname) + 3); name = alloc(STRLEN(fname) + 3);
if (name == NULL) if (name != NULL)
name = fname;
else
{ {
STRCPY(name, "<SNR>"); STRCPY(name, "<SNR>");
STRCPY(name + 5, fname + 3); STRCPY(name + 5, fname + 3);
@ -898,8 +903,8 @@ debuggy_find(
// Skip entries that are not useful or are for a line that is beyond // Skip entries that are not useful or are for a line that is beyond
// an already found breakpoint. // an already found breakpoint.
bp = &DEBUGGY(gap, i); bp = &DEBUGGY(gap, i);
if (((bp->dbg_type == DBG_FILE) == file && if (((bp->dbg_type == DBG_FILE) == is_file
bp->dbg_type != DBG_EXPR && ( && bp->dbg_type != DBG_EXPR && (
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
gap == &prof_ga || gap == &prof_ga ||
#endif #endif
@ -910,7 +915,10 @@ debuggy_find(
// while matching should abort it. // while matching should abort it.
prev_got_int = got_int; prev_got_int = got_int;
got_int = FALSE; got_int = FALSE;
if (vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0)) if ((name != NULL
&& vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0))
|| vim_regexec_prog(&bp->dbg_prog, FALSE,
short_name, (colnr_T)0))
{ {
lnum = bp->dbg_lnum; lnum = bp->dbg_lnum;
if (fp != NULL) if (fp != NULL)

View File

@ -489,7 +489,7 @@ EXCMD(CMD_digraphs, "digraphs", ex_digraphs,
EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),
EXCMD(CMD_disassemble, "disassemble", ex_disassemble, EXCMD(CMD_disassemble, "disassemble", ex_disassemble,
EX_EXTRA|EX_NEEDARG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK, EX_BANG|EX_EXTRA|EX_NEEDARG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE), ADDR_NONE),
EXCMD(CMD_djump, "djump", ex_findpat, EXCMD(CMD_djump, "djump", ex_findpat,
EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA, EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA,

View File

@ -866,7 +866,7 @@ do_cmdline(
if (do_profiling == PROF_YES) if (do_profiling == PROF_YES)
{ {
if (getline_is_func) if (getline_is_func)
func_line_start(real_cookie); func_line_start(real_cookie, SOURCING_LNUM);
else if (getline_equal(fgetline, cookie, getsourceline)) else if (getline_equal(fgetline, cookie, getsourceline))
script_line_start(); script_line_start();
} }

View File

@ -554,6 +554,51 @@ func_do_profile(ufunc_T *fp)
fp->uf_profiling = TRUE; fp->uf_profiling = TRUE;
} }
/*
* When calling a function: may initialize for profiling.
*/
void
profile_may_start_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc)
{
if (do_profiling == PROF_YES)
{
if (!fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL))
{
info->pi_started_profiling = TRUE;
func_do_profile(fp);
}
if (fp->uf_profiling
|| (fc->caller != NULL && fc->caller->func->uf_profiling))
{
++fp->uf_tm_count;
profile_start(&info->pi_call_start);
profile_zero(&fp->uf_tm_children);
}
script_prof_save(&info->pi_wait_start);
}
}
/*
* After calling a function: may handle profiling. profile_may_start_func()
* must have been called previously.
*/
void
profile_may_end_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc)
{
profile_end(&info->pi_call_start);
profile_sub_wait(&info->pi_wait_start, &info->pi_call_start);
profile_add(&fp->uf_tm_total, &info->pi_call_start);
profile_self(&fp->uf_tm_self, &info->pi_call_start, &fp->uf_tm_children);
if (fc->caller != NULL && fc->caller->func->uf_profiling)
{
profile_add(&fc->caller->func->uf_tm_children, &info->pi_call_start);
profile_add(&fc->caller->func->uf_tml_children, &info->pi_call_start);
}
if (info->pi_started_profiling)
// make a ":profdel func" stop profiling the function
fp->uf_profiling = FALSE;
}
/* /*
* Prepare profiling for entering a child or something else that is not * Prepare profiling for entering a child or something else that is not
* counted for the script/function itself. * counted for the script/function itself.
@ -597,15 +642,14 @@ prof_child_exit(
* until later and we need to store the time now. * until later and we need to store the time now.
*/ */
void void
func_line_start(void *cookie) func_line_start(void *cookie, long lnum)
{ {
funccall_T *fcp = (funccall_T *)cookie; funccall_T *fcp = (funccall_T *)cookie;
ufunc_T *fp = fcp->func; ufunc_T *fp = fcp->func;
if (fp->uf_profiling && SOURCING_LNUM >= 1 if (fp->uf_profiling && lnum >= 1 && lnum <= fp->uf_lines.ga_len)
&& SOURCING_LNUM <= fp->uf_lines.ga_len)
{ {
fp->uf_tml_idx = SOURCING_LNUM - 1; fp->uf_tml_idx = lnum - 1;
// Skip continuation lines. // Skip continuation lines.
while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL)
--fp->uf_tml_idx; --fp->uf_tml_idx;

View File

@ -19,9 +19,11 @@ void prof_inchar_enter(void);
void prof_inchar_exit(void); void prof_inchar_exit(void);
int prof_def_func(void); int prof_def_func(void);
void func_do_profile(ufunc_T *fp); void func_do_profile(ufunc_T *fp);
void profile_may_start_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc);
void profile_may_end_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc);
void prof_child_enter(proftime_T *tm); void prof_child_enter(proftime_T *tm);
void prof_child_exit(proftime_T *tm); void prof_child_exit(proftime_T *tm);
void func_line_start(void *cookie); void func_line_start(void *cookie, long lnum);
void func_line_exec(void *cookie); void func_line_exec(void *cookie);
void func_line_end(void *cookie); void func_line_end(void *cookie);
void script_do_profile(scriptitem_T *si); void script_do_profile(scriptitem_T *si);

View File

@ -3,6 +3,7 @@ int check_defined(char_u *p, size_t len, cctx_T *cctx);
int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2); int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2);
int use_typecheck(type_T *actual, type_T *expected); int use_typecheck(type_T *actual, type_T *expected);
int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const); int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const);
int func_needs_compiling(ufunc_T *ufunc, int profile);
int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx); int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx);
imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx); imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx);
imported_T *find_imported_in_script(char_u *name, size_t len, int sid); imported_T *find_imported_in_script(char_u *name, size_t len, int sid);
@ -15,7 +16,7 @@ void error_white_both(char_u *op, int len);
int assignment_len(char_u *p, int *heredoc); int assignment_len(char_u *p, int *heredoc);
void vim9_declare_error(char_u *name); void vim9_declare_error(char_u *name);
int check_vim9_unlet(char_u *name); int check_vim9_unlet(char_u *name);
int compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx); int compile_def_function(ufunc_T *ufunc, int check_return_type, int profiling, cctx_T *outer_cctx);
void set_function_type(ufunc_T *ufunc); void set_function_type(ufunc_T *ufunc);
void delete_instr(isn_T *isn); void delete_instr(isn_T *isn);
void unlink_def_function(ufunc_T *ufunc); void unlink_def_function(ufunc_T *ufunc);

View File

@ -1577,7 +1577,7 @@ typedef struct svar_S svar_T;
#if defined(FEAT_EVAL) || defined(PROTO) #if defined(FEAT_EVAL) || defined(PROTO)
typedef struct funccall_S funccall_T; typedef struct funccall_S funccall_T;
// values used for "uf_dfunc_idx" // values used for "uf_def_status"
typedef enum { typedef enum {
UF_NOT_COMPILED, UF_NOT_COMPILED,
UF_TO_BE_COMPILED, UF_TO_BE_COMPILED,
@ -1899,6 +1899,13 @@ typedef struct sn_prl_S
} sn_prl_T; } sn_prl_T;
# define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) # define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
typedef struct {
int pi_started_profiling;
proftime_T pi_wait_start;
proftime_T pi_call_start;
} profinfo_T;
# endif # endif
#else #else
// dummy typedefs for use in function prototypes // dummy typedefs for use in function prototypes

View File

@ -7,22 +7,27 @@ source shared.vim
source screendump.vim source screendump.vim
func Test_profile_func() func Test_profile_func()
call RunProfileFunc('func', 'let', 'let')
call RunProfileFunc('def', 'var', '')
endfunc
func RunProfileFunc(command, declare, assign)
let lines =<< trim [CODE] let lines =<< trim [CODE]
profile start Xprofile_func.log profile start Xprofile_func.log
profile func Foo* profile func Foo*
func! Foo1() XXX Foo1()
endfunc endXXX
func! Foo2() XXX Foo2()
let l:count = 100 DDD counter = 100
while l:count > 0 while counter > 0
let l:count = l:count - 1 AAA counter = counter - 1
endwhile endwhile
sleep 1m sleep 1m
endfunc endXXX
func! Foo3() XXX Foo3()
endfunc endXXX
func! Bar() XXX Bar()
endfunc endXXX
call Foo1() call Foo1()
call Foo1() call Foo1()
profile pause profile pause
@ -37,6 +42,10 @@ func Test_profile_func()
delfunc Foo3 delfunc Foo3
[CODE] [CODE]
call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') })
call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') })
call map(lines, {k, v -> substitute(v, 'AAA', a:assign, '') })
call writefile(lines, 'Xprofile_func.vim') call writefile(lines, 'Xprofile_func.vim')
call system(GetVimCommand() call system(GetVimCommand()
\ . ' -es --clean' \ . ' -es --clean'
@ -69,10 +78,10 @@ func Test_profile_func()
call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12]) call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12])
call assert_equal('', lines[13]) call assert_equal('', lines[13])
call assert_equal('count total (s) self (s)', lines[14]) call assert_equal('count total (s) self (s)', lines[14])
call assert_match('^\s*1\s\+.*\slet l:count = 100$', lines[15]) call assert_match('^\s*1\s\+.*\s\(let\|var\) counter = 100$', lines[15])
call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16]) call assert_match('^\s*101\s\+.*\swhile counter > 0$', lines[16])
call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17]) call assert_match('^\s*100\s\+.*\s \(let\)\= counter = counter - 1$', lines[17])
call assert_match('^\s*101\s\+.*\sendwhile$', lines[18]) call assert_match('^\s*10[01]\s\+.*\sendwhile$', lines[18])
call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19]) call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19])
call assert_equal('', lines[20]) call assert_equal('', lines[20])
call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21]) call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21])

View File

@ -1842,6 +1842,27 @@ def Test_silent()
res) res)
enddef enddef
def s:Profiled(): string
echo "profiled"
return "done"
enddef
def Test_profiled()
var res = execute('disass! s:Profiled')
assert_match('<SNR>\d*_Profiled\_s*' ..
'echo "profiled"\_s*' ..
'\d PROFILE START line 1\_s*' ..
'\d PUSHS "profiled"\_s*' ..
'\d ECHO 1\_s*' ..
'\d PROFILE END\_s*' ..
'return "done"\_s*' ..
'\d PROFILE START line 2\_s*' ..
'\d PUSHS "done"\_s*' ..
'\d RETURN\_s*' ..
'\d PROFILE END',
res)
enddef
def s:SilentReturn(): string def s:SilentReturn(): string
silent return "done" silent return "done"
enddef enddef

View File

@ -1601,12 +1601,14 @@ call_user_func(
char_u numbuf[NUMBUFLEN]; char_u numbuf[NUMBUFLEN];
char_u *name; char_u *name;
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
proftime_T wait_start; profinfo_T profile_info;
proftime_T call_start;
int started_profiling = FALSE;
#endif #endif
ESTACK_CHECK_DECLARATION ESTACK_CHECK_DECLARATION
#ifdef FEAT_PROFILE
CLEAR_FIELD(profile_info);
#endif
// If depth of calling is getting too high, don't execute the function. // If depth of calling is getting too high, don't execute the function.
if (funcdepth_increment() == FAIL) if (funcdepth_increment() == FAIL)
{ {
@ -1635,8 +1637,16 @@ call_user_func(
if (fp->uf_def_status != UF_NOT_COMPILED) if (fp->uf_def_status != UF_NOT_COMPILED)
{ {
// Execute the function, possibly compiling it first. // Execute the function, possibly compiling it first.
#ifdef FEAT_PROFILE
profile_may_start_func(&profile_info, fp, fc);
#endif
call_def_function(fp, argcount, argvars, funcexe->partial, rettv); call_def_function(fp, argcount, argvars, funcexe->partial, rettv);
funcdepth_decrement(); funcdepth_decrement();
#ifdef FEAT_PROFILE
if (do_profiling == PROF_YES && (fp->uf_profiling
|| (fc->caller != NULL && fc->caller->func->uf_profiling)))
profile_may_end_func(&profile_info, fp, fc);
#endif
current_funccal = fc->caller; current_funccal = fc->caller;
free_funccal(fc); free_funccal(fc);
return; return;
@ -1849,22 +1859,7 @@ call_user_func(
--no_wait_return; --no_wait_return;
} }
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
if (do_profiling == PROF_YES) profile_may_start_func(&profile_info, fp, fc);
{
if (!fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL))
{
started_profiling = TRUE;
func_do_profile(fp);
}
if (fp->uf_profiling
|| (fc->caller != NULL && fc->caller->func->uf_profiling))
{
++fp->uf_tm_count;
profile_start(&call_start);
profile_zero(&fp->uf_tm_children);
}
script_prof_save(&wait_start);
}
#endif #endif
save_current_sctx = current_sctx; save_current_sctx = current_sctx;
@ -1902,20 +1897,7 @@ call_user_func(
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
if (do_profiling == PROF_YES && (fp->uf_profiling if (do_profiling == PROF_YES && (fp->uf_profiling
|| (fc->caller != NULL && fc->caller->func->uf_profiling))) || (fc->caller != NULL && fc->caller->func->uf_profiling)))
{ profile_may_end_func(&profile_info, fp, fc);
profile_end(&call_start);
profile_sub_wait(&wait_start, &call_start);
profile_add(&fp->uf_tm_total, &call_start);
profile_self(&fp->uf_tm_self, &call_start, &fp->uf_tm_children);
if (fc->caller != NULL && fc->caller->func->uf_profiling)
{
profile_add(&fc->caller->func->uf_tm_children, &call_start);
profile_add(&fc->caller->func->uf_tml_children, &call_start);
}
if (started_profiling)
// make a ":profdel func" stop profiling the function
fp->uf_profiling = FALSE;
}
#endif #endif
// when being verbose, mention the return value // when being verbose, mention the return value
@ -1964,7 +1946,7 @@ call_user_func(
current_sctx = save_current_sctx; current_sctx = save_current_sctx;
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
if (do_profiling == PROF_YES) if (do_profiling == PROF_YES)
script_prof_restore(&wait_start); script_prof_restore(&profile_info.pi_wait_start);
#endif #endif
if (using_sandbox) if (using_sandbox)
--sandbox; --sandbox;
@ -3982,7 +3964,7 @@ ex_function(exarg_T *eap)
/* /*
* :defcompile - compile all :def functions in the current script that need to * :defcompile - compile all :def functions in the current script that need to
* be compiled. Except dead functions. * be compiled. Except dead functions. Doesn't do profiling.
*/ */
void void
ex_defcompile(exarg_T *eap UNUSED) ex_defcompile(exarg_T *eap UNUSED)
@ -4002,7 +3984,7 @@ ex_defcompile(exarg_T *eap UNUSED)
&& ufunc->uf_def_status == UF_TO_BE_COMPILED && ufunc->uf_def_status == UF_TO_BE_COMPILED
&& (ufunc->uf_flags & FC_DEAD) == 0) && (ufunc->uf_flags & FC_DEAD) == 0)
{ {
compile_def_function(ufunc, FALSE, NULL); compile_def_function(ufunc, FALSE, FALSE, NULL);
if (func_hashtab.ht_changed != changed) if (func_hashtab.ht_changed != changed)
{ {
@ -4698,7 +4680,7 @@ get_func_line(
SOURCING_LNUM = fcp->linenr; SOURCING_LNUM = fcp->linenr;
#ifdef FEAT_PROFILE #ifdef FEAT_PROFILE
if (do_profiling == PROF_YES) if (do_profiling == PROF_YES)
func_line_start(cookie); func_line_start(cookie, SOURCING_LNUM);
#endif #endif
} }
} }

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
2400,
/**/ /**/
2399, 2399,
/**/ /**/

View File

@ -152,6 +152,9 @@ typedef enum {
ISN_CMDMOD, // set cmdmod ISN_CMDMOD, // set cmdmod
ISN_CMDMOD_REV, // undo ISN_CMDMOD ISN_CMDMOD_REV, // undo ISN_CMDMOD
ISN_PROF_START, // start a line for profiling
ISN_PROF_END, // end a line for profiling
ISN_UNPACK, // unpack list into items, uses isn_arg.unpack ISN_UNPACK, // unpack list into items, uses isn_arg.unpack
ISN_SHUFFLE, // move item on stack up or down ISN_SHUFFLE, // move item on stack up or down
ISN_DROP // pop stack and discard value ISN_DROP // pop stack and discard value
@ -366,8 +369,12 @@ struct dfunc_S {
// was compiled. // was compiled.
garray_T df_def_args_isn; // default argument instructions garray_T df_def_args_isn; // default argument instructions
// After compiling "df_instr" and/or "df_instr_prof" is not NULL.
isn_T *df_instr; // function body to be executed isn_T *df_instr; // function body to be executed
int df_instr_count; int df_instr_count; // size of "df_instr"
isn_T *df_instr_prof; // like "df_instr" with profiling
int df_instr_prof_count; // size of "df_instr_prof"
int df_varcount; // number of local variables int df_varcount; // number of local variables
int df_has_closure; // one if a closure was created int df_has_closure; // one if a closure was created

View File

@ -123,6 +123,8 @@ struct cctx_S {
char_u *ctx_line_start; // start of current line or NULL char_u *ctx_line_start; // start of current line or NULL
garray_T ctx_instr; // generated instructions garray_T ctx_instr; // generated instructions
int ctx_profiling; // when TRUE generate ISN_PROF_START
garray_T ctx_locals; // currently visible local variables garray_T ctx_locals; // currently visible local variables
int ctx_locals_count; // total number of local variables int ctx_locals_count; // total number of local variables
@ -1692,6 +1694,29 @@ generate_BLOBAPPEND(cctx_T *cctx)
return OK; return OK;
} }
/*
* Return TRUE if "ufunc" should be compiled, taking into account whether
* "profile" indicates profiling is to be done.
*/
int
func_needs_compiling(ufunc_T *ufunc, int profile)
{
switch (ufunc->uf_def_status)
{
case UF_NOT_COMPILED: return FALSE;
case UF_TO_BE_COMPILED: return TRUE;
case UF_COMPILED:
{
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ ufunc->uf_dfunc_idx;
return profile ? dfunc->df_instr_prof == NULL
: dfunc->df_instr == NULL;
}
case UF_COMPILING: return FALSE;
}
}
/* /*
* Generate an ISN_DCALL or ISN_UCALL instruction. * Generate an ISN_DCALL or ISN_UCALL instruction.
* Return FAIL if the number of arguments is wrong. * Return FAIL if the number of arguments is wrong.
@ -1744,9 +1769,9 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount)
return FAIL; return FAIL;
} }
} }
if (ufunc->uf_def_status == UF_TO_BE_COMPILED) if (func_needs_compiling(ufunc, cctx->ctx_profiling)
if (compile_def_function(ufunc, ufunc->uf_ret_type == NULL, NULL) && compile_def_function(ufunc, ufunc->uf_ret_type == NULL,
== FAIL) cctx->ctx_profiling, NULL) == FAIL)
return FAIL; return FAIL;
} }
@ -2063,6 +2088,19 @@ generate_undo_cmdmods(cctx_T *cctx)
return OK; return OK;
} }
static void
may_generate_prof_end(cctx_T *cctx, int prof_lnum)
{
if (cctx->ctx_profiling && prof_lnum >= 0)
{
int save_lnum = cctx->ctx_lnum;
cctx->ctx_lnum = prof_lnum;
generate_instr(cctx, ISN_PROF_END);
cctx->ctx_lnum = save_lnum;
}
}
/* /*
* Reserve space for a local variable. * Reserve space for a local variable.
* Return the variable or NULL if it failed. * Return the variable or NULL if it failed.
@ -2575,8 +2613,9 @@ generate_funcref(cctx_T *cctx, char_u *name)
return FAIL; return FAIL;
// Need to compile any default values to get the argument types. // Need to compile any default values to get the argument types.
if (ufunc->uf_def_status == UF_TO_BE_COMPILED) if (func_needs_compiling(ufunc, cctx->ctx_profiling)
if (compile_def_function(ufunc, TRUE, NULL) == FAIL) && compile_def_function(ufunc, TRUE, cctx->ctx_profiling, NULL)
== FAIL)
return FAIL; return FAIL;
return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type); return generate_PUSHFUNC(cctx, ufunc->uf_name, ufunc->uf_func_type);
} }
@ -3070,7 +3109,7 @@ compile_lambda(char_u **arg, cctx_T *cctx)
clear_tv(&rettv); clear_tv(&rettv);
// Compile the function into instructions. // Compile the function into instructions.
compile_def_function(ufunc, TRUE, cctx); compile_def_function(ufunc, TRUE, cctx->ctx_profiling, cctx);
clear_evalarg(&evalarg, NULL); clear_evalarg(&evalarg, NULL);
@ -5047,8 +5086,9 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx)
r = eap->skip ? OK : FAIL; r = eap->skip ? OK : FAIL;
goto theend; goto theend;
} }
if (ufunc->uf_def_status == UF_TO_BE_COMPILED if (func_needs_compiling(ufunc, cctx->ctx_profiling)
&& compile_def_function(ufunc, TRUE, cctx) == FAIL) && compile_def_function(ufunc, TRUE, cctx->ctx_profiling, cctx)
== FAIL)
{ {
func_ptr_unref(ufunc); func_ptr_unref(ufunc);
goto theend; goto theend;
@ -7101,7 +7141,11 @@ compile_while(char_u *arg, cctx_T *cctx)
if (scope == NULL) if (scope == NULL)
return NULL; return NULL;
// "endwhile" jumps back here, one before when profiling
scope->se_u.se_while.ws_top_label = instr->ga_len; scope->se_u.se_while.ws_top_label = instr->ga_len;
if (cctx->ctx_profiling && ((isn_T *)instr->ga_data)[instr->ga_len - 1]
.isn_type == ISN_PROF_START)
--scope->se_u.se_while.ws_top_label;
// compile "expr" // compile "expr"
if (compile_expr0(&p, cctx) == FAIL) if (compile_expr0(&p, cctx) == FAIL)
@ -7134,6 +7178,9 @@ compile_endwhile(char_u *arg, cctx_T *cctx)
cctx->ctx_scope = scope->se_outer; cctx->ctx_scope = scope->se_outer;
unwind_locals(cctx, scope->se_local_count); unwind_locals(cctx, scope->se_local_count);
// count the endwhile before jumping
may_generate_prof_end(cctx, cctx->ctx_lnum);
// At end of ":for" scope jump back to the FOR instruction. // At end of ":for" scope jump back to the FOR instruction.
generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label); generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label);
@ -7794,13 +7841,18 @@ add_def_function(ufunc_T *ufunc)
* When "check_return_type" is set then set ufunc->uf_ret_type to the type of * When "check_return_type" is set then set ufunc->uf_ret_type to the type of
* the return statement (used for lambda). When uf_ret_type is already set * the return statement (used for lambda). When uf_ret_type is already set
* then check that it matches. * then check that it matches.
* When "profiling" is true add ISN_PROF_START instructions.
* "outer_cctx" is set for a nested function. * "outer_cctx" is set for a nested function.
* This can be used recursively through compile_lambda(), which may reallocate * This can be used recursively through compile_lambda(), which may reallocate
* "def_functions". * "def_functions".
* Returns OK or FAIL. * Returns OK or FAIL.
*/ */
int int
compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx) compile_def_function(
ufunc_T *ufunc,
int check_return_type,
int profiling,
cctx_T *outer_cctx)
{ {
char_u *line = NULL; char_u *line = NULL;
char_u *p; char_u *p;
@ -7813,6 +7865,7 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx)
int save_estack_compiling = estack_compiling; int save_estack_compiling = estack_compiling;
int do_estack_push; int do_estack_push;
int new_def_function = FALSE; int new_def_function = FALSE;
int prof_lnum = -1;
// When using a function that was compiled before: Free old instructions. // When using a function that was compiled before: Free old instructions.
// The index is reused. Otherwise add a new entry in "def_functions". // The index is reused. Otherwise add a new entry in "def_functions".
@ -7832,6 +7885,8 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx)
ufunc->uf_def_status = UF_COMPILING; ufunc->uf_def_status = UF_COMPILING;
CLEAR_FIELD(cctx); CLEAR_FIELD(cctx);
cctx.ctx_profiling = profiling;
cctx.ctx_ufunc = ufunc; cctx.ctx_ufunc = ufunc;
cctx.ctx_lnum = -1; cctx.ctx_lnum = -1;
cctx.ctx_outer = outer_cctx; cctx.ctx_outer = outer_cctx;
@ -7932,22 +7987,35 @@ compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx)
{ {
line = next_line_from_context(&cctx, FALSE); line = next_line_from_context(&cctx, FALSE);
if (cctx.ctx_lnum >= ufunc->uf_lines.ga_len) if (cctx.ctx_lnum >= ufunc->uf_lines.ga_len)
{
// beyond the last line // beyond the last line
may_generate_prof_end(&cctx, prof_lnum);
break; break;
} }
}
CLEAR_FIELD(ea); CLEAR_FIELD(ea);
ea.cmdlinep = &line; ea.cmdlinep = &line;
ea.cmd = skipwhite(line); ea.cmd = skipwhite(line);
// Some things can be recognized by the first character. if (*ea.cmd == '#')
switch (*ea.cmd)
{ {
case '#':
// "#" starts a comment // "#" starts a comment
line = (char_u *)""; line = (char_u *)"";
continue; continue;
}
if (cctx.ctx_profiling && cctx.ctx_lnum != prof_lnum)
{
may_generate_prof_end(&cctx, prof_lnum);
prof_lnum = cctx.ctx_lnum;
generate_instr(&cctx, ISN_PROF_START);
}
// Some things can be recognized by the first character.
switch (*ea.cmd)
{
case '}': case '}':
{ {
// "}" ends a block scope // "}" ends a block scope
@ -8308,8 +8376,16 @@ nextline:
+ ufunc->uf_dfunc_idx; + ufunc->uf_dfunc_idx;
dfunc->df_deleted = FALSE; dfunc->df_deleted = FALSE;
dfunc->df_script_seq = current_sctx.sc_seq; dfunc->df_script_seq = current_sctx.sc_seq;
if (cctx.ctx_profiling)
{
dfunc->df_instr_prof = instr->ga_data;
dfunc->df_instr_prof_count = instr->ga_len;
}
else
{
dfunc->df_instr = instr->ga_data; dfunc->df_instr = instr->ga_data;
dfunc->df_instr_count = instr->ga_len; dfunc->df_instr_count = instr->ga_len;
}
dfunc->df_varcount = cctx.ctx_locals_count; dfunc->df_varcount = cctx.ctx_locals_count;
dfunc->df_has_closure = cctx.ctx_has_closure; dfunc->df_has_closure = cctx.ctx_has_closure;
if (cctx.ctx_outer_used) if (cctx.ctx_outer_used)
@ -8586,6 +8662,8 @@ delete_instr(isn_T *isn)
case ISN_OPNR: case ISN_OPNR:
case ISN_PCALL: case ISN_PCALL:
case ISN_PCALL_END: case ISN_PCALL_END:
case ISN_PROF_END:
case ISN_PROF_START:
case ISN_PUSHBOOL: case ISN_PUSHBOOL:
case ISN_PUSHF: case ISN_PUSHF:
case ISN_PUSHNR: case ISN_PUSHNR:

View File

@ -294,7 +294,7 @@ call_dfunc(int cdf_idx, partial_T *pt, int argcount_arg, ectx_T *ectx)
// Set execution state to the start of the called function. // Set execution state to the start of the called function.
ectx->ec_dfunc_idx = cdf_idx; ectx->ec_dfunc_idx = cdf_idx;
ectx->ec_instr = dfunc->df_instr; ectx->ec_instr = dfunc->df_instr;
entry = estack_push_ufunc(dfunc->df_ufunc, 1); entry = estack_push_ufunc(ufunc, 1);
if (entry != NULL) if (entry != NULL)
{ {
// Set the script context to the script where the function was defined. // Set the script context to the script where the function was defined.
@ -645,9 +645,10 @@ call_ufunc(
int error; int error;
int idx; int idx;
int did_emsg_before = did_emsg; int did_emsg_before = did_emsg;
int profiling = do_profiling == PROF_YES && ufunc->uf_profiling;
if (ufunc->uf_def_status == UF_TO_BE_COMPILED if (func_needs_compiling(ufunc, profiling)
&& compile_def_function(ufunc, FALSE, NULL) == FAIL) && compile_def_function(ufunc, FALSE, profiling, NULL) == FAIL)
return FAIL; return FAIL;
if (ufunc->uf_def_status == UF_COMPILED) if (ufunc->uf_def_status == UF_COMPILED)
{ {
@ -1130,6 +1131,7 @@ call_def_function(
int save_did_emsg_def = did_emsg_def; int save_did_emsg_def = did_emsg_def;
int trylevel_at_start = trylevel; int trylevel_at_start = trylevel;
int orig_funcdepth; int orig_funcdepth;
int profiling = do_profiling == PROF_YES && ufunc->uf_profiling;
// Get pointer to item in the stack. // Get pointer to item in the stack.
#define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx) #define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
@ -1142,8 +1144,9 @@ call_def_function(
#define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx) #define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx)
if (ufunc->uf_def_status == UF_NOT_COMPILED if (ufunc->uf_def_status == UF_NOT_COMPILED
|| (ufunc->uf_def_status == UF_TO_BE_COMPILED || (func_needs_compiling(ufunc, profiling)
&& compile_def_function(ufunc, FALSE, NULL) == FAIL)) && compile_def_function(ufunc, FALSE, profiling, NULL)
== FAIL))
{ {
if (did_emsg_cumul + did_emsg == did_emsg_before) if (did_emsg_cumul + did_emsg == did_emsg_before)
semsg(_(e_function_is_not_compiled_str), semsg(_(e_function_is_not_compiled_str),
@ -1155,7 +1158,7 @@ call_def_function(
// Check the function was really compiled. // Check the function was really compiled.
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ ufunc->uf_dfunc_idx; + ufunc->uf_dfunc_idx;
if (dfunc->df_instr == NULL) if ((profiling ? dfunc->df_instr_prof : dfunc->df_instr) == NULL)
{ {
iemsg("using call_def_function() on not compiled function"); iemsg("using call_def_function() on not compiled function");
return FAIL; return FAIL;
@ -1294,7 +1297,7 @@ call_def_function(
++ectx.ec_stack.ga_len; ++ectx.ec_stack.ga_len;
} }
ectx.ec_instr = dfunc->df_instr; ectx.ec_instr = profiling ? dfunc->df_instr_prof : dfunc->df_instr;
} }
// Following errors are in the function, not the caller. // Following errors are in the function, not the caller.
@ -3495,6 +3498,26 @@ call_def_function(
} }
break; break;
case ISN_PROF_START:
case ISN_PROF_END:
{
funccall_T cookie;
ufunc_T *cur_ufunc =
(((dfunc_T *)def_functions.ga_data)
+ ectx.ec_dfunc_idx)->df_ufunc;
cookie.func = cur_ufunc;
if (iptr->isn_type == ISN_PROF_START)
{
func_line_start(&cookie, iptr->isn_lnum);
// if we get here the instruction is executed
func_line_exec(&cookie);
}
else
func_line_end(&cookie);
}
break;
case ISN_SHUFFLE: case ISN_SHUFFLE:
{ {
typval_T tmp_tv; typval_T tmp_tv;
@ -3642,6 +3665,7 @@ ex_disassemble(exarg_T *eap)
ufunc_T *ufunc; ufunc_T *ufunc;
dfunc_T *dfunc; dfunc_T *dfunc;
isn_T *instr; isn_T *instr;
int instr_count;
int current; int current;
int line_idx = 0; int line_idx = 0;
int prev_current = 0; int prev_current = 0;
@ -3677,8 +3701,8 @@ ex_disassemble(exarg_T *eap)
semsg(_(e_cannot_find_function_str), eap->arg); semsg(_(e_cannot_find_function_str), eap->arg);
return; return;
} }
if (ufunc->uf_def_status == UF_TO_BE_COMPILED if (func_needs_compiling(ufunc, eap->forceit)
&& compile_def_function(ufunc, FALSE, NULL) == FAIL) && compile_def_function(ufunc, FALSE, eap->forceit, NULL) == FAIL)
return; return;
if (ufunc->uf_def_status != UF_COMPILED) if (ufunc->uf_def_status != UF_COMPILED)
{ {
@ -3691,8 +3715,10 @@ ex_disassemble(exarg_T *eap)
msg((char *)ufunc->uf_name); msg((char *)ufunc->uf_name);
dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
instr = dfunc->df_instr; instr = eap->forceit ? dfunc->df_instr_prof : dfunc->df_instr;
for (current = 0; current < dfunc->df_instr_count; ++current) instr_count = eap->forceit ? dfunc->df_instr_prof_count
: dfunc->df_instr_count;
for (current = 0; current < instr_count; ++current)
{ {
isn_T *iptr = &instr[current]; isn_T *iptr = &instr[current];
char *line; char *line;
@ -4319,6 +4345,14 @@ ex_disassemble(exarg_T *eap)
} }
case ISN_CMDMOD_REV: smsg("%4d CMDMOD_REV", current); break; case ISN_CMDMOD_REV: smsg("%4d CMDMOD_REV", current); break;
case ISN_PROF_START:
smsg("%4d PROFILE START line %d", current, iptr->isn_lnum);
break;
case ISN_PROF_END:
smsg("%4d PROFILE END", current);
break;
case ISN_UNPACK: smsg("%4d UNPACK %d%s", current, case ISN_UNPACK: smsg("%4d UNPACK %d%s", current,
iptr->isn_arg.unpack.unp_count, iptr->isn_arg.unpack.unp_count,
iptr->isn_arg.unpack.unp_semicolon ? " semicolon" : ""); iptr->isn_arg.unpack.unp_semicolon ? " semicolon" : "");

View File

@ -336,7 +336,8 @@ typval2type_int(typval_T *tv, garray_T *type_gap)
// May need to get the argument types from default values by // May need to get the argument types from default values by
// compiling the function. // compiling the function.
if (ufunc->uf_def_status == UF_TO_BE_COMPILED if (ufunc->uf_def_status == UF_TO_BE_COMPILED
&& compile_def_function(ufunc, TRUE, NULL) == FAIL) && compile_def_function(ufunc, TRUE, FALSE, NULL)
== FAIL)
return NULL; return NULL;
if (ufunc->uf_func_type == NULL) if (ufunc->uf_func_type == NULL)
set_function_type(ufunc); set_function_type(ufunc);