Skip to content

Commit b204990

Browse files
committed
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.
1 parent 7cf0c11 commit b204990

16 files changed

Lines changed: 311 additions & 109 deletions

runtime/doc/repeat.txt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*repeat.txt* For Vim version 8.2. Last change: 2021 Jan 02
1+
*repeat.txt* For Vim version 8.2. Last change: 2021 Jan 23
22

33

44
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|.
354354
Vim version, or update Vim to a newer version. See
355355
|vimscript-version| for what changed between versions.
356356

357-
:vim9script [noclear] *:vim9* *:vim9script*
357+
:vim9[script] [noclear] *:vim9* *:vim9script*
358358
Marks a script file as containing |Vim9-script|
359359
commands. Also see |vim9-namespace|.
360360
Must be the first command in the file.
@@ -899,8 +899,9 @@ matches ".../plugin/explorer.vim", ".../plugin/iexplorer.vim", etc. >
899899
matches ".../plugin/explorer.vim" and "explorer.vim" in any other directory.
900900

901901
The match for functions is done against the name as it's shown in the output
902-
of ":function". For local functions this means that something like "<SNR>99_"
903-
is prepended.
902+
of ":function". However, for local functions the script-specific prefix such
903+
as "<SNR>99_" is ignored to make it easier to match script-local functions
904+
without knowing the ID of the script.
904905

905906
Note that functions are first loaded and later executed. When they are loaded
906907
the "file" breakpoints are checked, when they are executed the "func"
@@ -955,10 +956,10 @@ OBSCURE
955956

956957
Profiling means that Vim measures the time that is spent on executing
957958
functions and/or scripts. The |+profile| feature is required for this.
958-
It is only included when Vim was compiled with "huge" features.
959+
It is included when Vim was compiled with "huge" features.
959960

960961
You can also use the |reltime()| function to measure time. This only requires
961-
the |+reltime| feature, which is present more often.
962+
the |+reltime| feature, which is present in more builds.
962963

963964
For profiling syntax highlighting see |:syntime|.
964965

@@ -1005,7 +1006,12 @@ For example, to profile the one_script.vim script file: >
10051006

10061007

10071008
You must always start with a ":profile start fname" command. The resulting
1008-
file is written when Vim exits. Here is an example of the output, with line
1009+
file is written when Vim exits. For example, to profile one specific
1010+
function: >
1011+
profile start /tmp/vimprofile
1012+
profile func MyFunc
1013+
1014+
Here is an example of the output, with line
10091015
numbers prepended for the explanation:
10101016

10111017
1 FUNCTION Test2() ~

src/debugger.c

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ has_profiling(
864864
*/
865865
static linenr_T
866866
debuggy_find(
867-
int file, // TRUE for a file, FALSE for a function
867+
int is_file, // TRUE for a file, FALSE for a function
868868
char_u *fname, // file or function name
869869
linenr_T after, // after this line number
870870
garray_T *gap, // either &dbg_breakp or &prof_ga
@@ -873,20 +873,25 @@ debuggy_find(
873873
struct debuggy *bp;
874874
int i;
875875
linenr_T lnum = 0;
876-
char_u *name = fname;
876+
char_u *name = NULL;
877+
char_u *short_name = fname;
877878
int prev_got_int;
878879

879880
// Return quickly when there are no breakpoints.
880881
if (gap->ga_len == 0)
881882
return (linenr_T)0;
882883

883-
// Replace K_SNR in function name with "<SNR>".
884-
if (!file && fname[0] == K_SPECIAL)
884+
// For a script-local function remove the prefix, so that
885+
// "profile func Func" matches "Func" in any script. Otherwise it's very
886+
// difficult to profile/debug a script-local function. It may match a
887+
// function in the wrong script, but that is much better than not being
888+
// able to profile/debug a function in a script with unknown ID.
889+
// Also match a script-specific name.
890+
if (!is_file && fname[0] == K_SPECIAL)
885891
{
892+
short_name = vim_strchr(fname, '_') + 1;
886893
name = alloc(STRLEN(fname) + 3);
887-
if (name == NULL)
888-
name = fname;
889-
else
894+
if (name != NULL)
890895
{
891896
STRCPY(name, "<SNR>");
892897
STRCPY(name + 5, fname + 3);
@@ -898,8 +903,8 @@ debuggy_find(
898903
// Skip entries that are not useful or are for a line that is beyond
899904
// an already found breakpoint.
900905
bp = &DEBUGGY(gap, i);
901-
if (((bp->dbg_type == DBG_FILE) == file &&
902-
bp->dbg_type != DBG_EXPR && (
906+
if (((bp->dbg_type == DBG_FILE) == is_file
907+
&& bp->dbg_type != DBG_EXPR && (
903908
#ifdef FEAT_PROFILE
904909
gap == &prof_ga ||
905910
#endif
@@ -910,7 +915,10 @@ debuggy_find(
910915
// while matching should abort it.
911916
prev_got_int = got_int;
912917
got_int = FALSE;
913-
if (vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0))
918+
if ((name != NULL
919+
&& vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0))
920+
|| vim_regexec_prog(&bp->dbg_prog, FALSE,
921+
short_name, (colnr_T)0))
914922
{
915923
lnum = bp->dbg_lnum;
916924
if (fp != NULL)

src/ex_cmds.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ EXCMD(CMD_digraphs, "digraphs", ex_digraphs,
489489
EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
490490
ADDR_NONE),
491491
EXCMD(CMD_disassemble, "disassemble", ex_disassemble,
492-
EX_EXTRA|EX_NEEDARG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
492+
EX_BANG|EX_EXTRA|EX_NEEDARG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
493493
ADDR_NONE),
494494
EXCMD(CMD_djump, "djump", ex_findpat,
495495
EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA,

src/ex_docmd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ do_cmdline(
866866
if (do_profiling == PROF_YES)
867867
{
868868
if (getline_is_func)
869-
func_line_start(real_cookie);
869+
func_line_start(real_cookie, SOURCING_LNUM);
870870
else if (getline_equal(fgetline, cookie, getsourceline))
871871
script_line_start();
872872
}

src/profiler.c

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,51 @@ func_do_profile(ufunc_T *fp)
554554
fp->uf_profiling = TRUE;
555555
}
556556

557+
/*
558+
* When calling a function: may initialize for profiling.
559+
*/
560+
void
561+
profile_may_start_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc)
562+
{
563+
if (do_profiling == PROF_YES)
564+
{
565+
if (!fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL))
566+
{
567+
info->pi_started_profiling = TRUE;
568+
func_do_profile(fp);
569+
}
570+
if (fp->uf_profiling
571+
|| (fc->caller != NULL && fc->caller->func->uf_profiling))
572+
{
573+
++fp->uf_tm_count;
574+
profile_start(&info->pi_call_start);
575+
profile_zero(&fp->uf_tm_children);
576+
}
577+
script_prof_save(&info->pi_wait_start);
578+
}
579+
}
580+
581+
/*
582+
* After calling a function: may handle profiling. profile_may_start_func()
583+
* must have been called previously.
584+
*/
585+
void
586+
profile_may_end_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc)
587+
{
588+
profile_end(&info->pi_call_start);
589+
profile_sub_wait(&info->pi_wait_start, &info->pi_call_start);
590+
profile_add(&fp->uf_tm_total, &info->pi_call_start);
591+
profile_self(&fp->uf_tm_self, &info->pi_call_start, &fp->uf_tm_children);
592+
if (fc->caller != NULL && fc->caller->func->uf_profiling)
593+
{
594+
profile_add(&fc->caller->func->uf_tm_children, &info->pi_call_start);
595+
profile_add(&fc->caller->func->uf_tml_children, &info->pi_call_start);
596+
}
597+
if (info->pi_started_profiling)
598+
// make a ":profdel func" stop profiling the function
599+
fp->uf_profiling = FALSE;
600+
}
601+
557602
/*
558603
* Prepare profiling for entering a child or something else that is not
559604
* counted for the script/function itself.
@@ -597,15 +642,14 @@ prof_child_exit(
597642
* until later and we need to store the time now.
598643
*/
599644
void
600-
func_line_start(void *cookie)
645+
func_line_start(void *cookie, long lnum)
601646
{
602647
funccall_T *fcp = (funccall_T *)cookie;
603648
ufunc_T *fp = fcp->func;
604649

605-
if (fp->uf_profiling && SOURCING_LNUM >= 1
606-
&& SOURCING_LNUM <= fp->uf_lines.ga_len)
650+
if (fp->uf_profiling && lnum >= 1 && lnum <= fp->uf_lines.ga_len)
607651
{
608-
fp->uf_tml_idx = SOURCING_LNUM - 1;
652+
fp->uf_tml_idx = lnum - 1;
609653
// Skip continuation lines.
610654
while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL)
611655
--fp->uf_tml_idx;

src/proto/profiler.pro

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ void prof_inchar_enter(void);
1919
void prof_inchar_exit(void);
2020
int prof_def_func(void);
2121
void func_do_profile(ufunc_T *fp);
22+
void profile_may_start_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc);
23+
void profile_may_end_func(profinfo_T *info, ufunc_T *fp, funccall_T *fc);
2224
void prof_child_enter(proftime_T *tm);
2325
void prof_child_exit(proftime_T *tm);
24-
void func_line_start(void *cookie);
26+
void func_line_start(void *cookie, long lnum);
2527
void func_line_exec(void *cookie);
2628
void func_line_end(void *cookie);
2729
void script_do_profile(scriptitem_T *si);

src/proto/vim9compile.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ int check_defined(char_u *p, size_t len, cctx_T *cctx);
33
int check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2);
44
int use_typecheck(type_T *actual, type_T *expected);
55
int need_type(type_T *actual, type_T *expected, int offset, int arg_idx, cctx_T *cctx, int silent, int actual_is_const);
6+
int func_needs_compiling(ufunc_T *ufunc, int profile);
67
int get_script_item_idx(int sid, char_u *name, int check_writable, cctx_T *cctx);
78
imported_T *find_imported(char_u *name, size_t len, cctx_T *cctx);
89
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);
1516
int assignment_len(char_u *p, int *heredoc);
1617
void vim9_declare_error(char_u *name);
1718
int check_vim9_unlet(char_u *name);
18-
int compile_def_function(ufunc_T *ufunc, int check_return_type, cctx_T *outer_cctx);
19+
int compile_def_function(ufunc_T *ufunc, int check_return_type, int profiling, cctx_T *outer_cctx);
1920
void set_function_type(ufunc_T *ufunc);
2021
void delete_instr(isn_T *isn);
2122
void unlink_def_function(ufunc_T *ufunc);

src/structs.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ typedef struct svar_S svar_T;
15771577
#if defined(FEAT_EVAL) || defined(PROTO)
15781578
typedef struct funccall_S funccall_T;
15791579

1580-
// values used for "uf_dfunc_idx"
1580+
// values used for "uf_def_status"
15811581
typedef enum {
15821582
UF_NOT_COMPILED,
15831583
UF_TO_BE_COMPILED,
@@ -1899,6 +1899,13 @@ typedef struct sn_prl_S
18991899
} sn_prl_T;
19001900

19011901
# define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
1902+
1903+
typedef struct {
1904+
int pi_started_profiling;
1905+
proftime_T pi_wait_start;
1906+
proftime_T pi_call_start;
1907+
} profinfo_T;
1908+
19021909
# endif
19031910
#else
19041911
// dummy typedefs for use in function prototypes

src/testdir/test_profile.vim

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ source shared.vim
77
source screendump.vim
88

99
func Test_profile_func()
10+
call RunProfileFunc('func', 'let', 'let')
11+
call RunProfileFunc('def', 'var', '')
12+
endfunc
13+
14+
func RunProfileFunc(command, declare, assign)
1015
let lines =<< trim [CODE]
1116
profile start Xprofile_func.log
1217
profile func Foo*
13-
func! Foo1()
14-
endfunc
15-
func! Foo2()
16-
let l:count = 100
17-
while l:count > 0
18-
let l:count = l:count - 1
18+
XXX Foo1()
19+
endXXX
20+
XXX Foo2()
21+
DDD counter = 100
22+
while counter > 0
23+
AAA counter = counter - 1
1924
endwhile
2025
sleep 1m
21-
endfunc
22-
func! Foo3()
23-
endfunc
24-
func! Bar()
25-
endfunc
26+
endXXX
27+
XXX Foo3()
28+
endXXX
29+
XXX Bar()
30+
endXXX
2631
call Foo1()
2732
call Foo1()
2833
profile pause
@@ -37,6 +42,10 @@ func Test_profile_func()
3742
delfunc Foo3
3843
[CODE]
3944

45+
call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') })
46+
call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') })
47+
call map(lines, {k, v -> substitute(v, 'AAA', a:assign, '') })
48+
4049
call writefile(lines, 'Xprofile_func.vim')
4150
call system(GetVimCommand()
4251
\ . ' -es --clean'
@@ -69,10 +78,10 @@ func Test_profile_func()
6978
call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12])
7079
call assert_equal('', lines[13])
7180
call assert_equal('count total (s) self (s)', lines[14])
72-
call assert_match('^\s*1\s\+.*\slet l:count = 100$', lines[15])
73-
call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16])
74-
call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17])
75-
call assert_match('^\s*101\s\+.*\sendwhile$', lines[18])
81+
call assert_match('^\s*1\s\+.*\s\(let\|var\) counter = 100$', lines[15])
82+
call assert_match('^\s*101\s\+.*\swhile counter > 0$', lines[16])
83+
call assert_match('^\s*100\s\+.*\s \(let\)\= counter = counter - 1$', lines[17])
84+
call assert_match('^\s*10[01]\s\+.*\sendwhile$', lines[18])
7685
call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19])
7786
call assert_equal('', lines[20])
7887
call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21])

src/testdir/test_vim9_disassemble.vim

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,27 @@ def Test_silent()
18421842
res)
18431843
enddef
18441844

1845+
def s:Profiled(): string
1846+
echo "profiled"
1847+
return "done"
1848+
enddef
1849+
1850+
def Test_profiled()
1851+
var res = execute('disass! s:Profiled')
1852+
assert_match('<SNR>\d*_Profiled\_s*' ..
1853+
'echo "profiled"\_s*' ..
1854+
'\d PROFILE START line 1\_s*' ..
1855+
'\d PUSHS "profiled"\_s*' ..
1856+
'\d ECHO 1\_s*' ..
1857+
'\d PROFILE END\_s*' ..
1858+
'return "done"\_s*' ..
1859+
'\d PROFILE START line 2\_s*' ..
1860+
'\d PUSHS "done"\_s*' ..
1861+
'\d RETURN\_s*' ..
1862+
'\d PROFILE END',
1863+
res)
1864+
enddef
1865+
18451866
def s:SilentReturn(): string
18461867
silent return "done"
18471868
enddef

0 commit comments

Comments
 (0)