From db7b3f1b92f7f911a09142ff784d24a09b00ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 2 Nov 2023 06:33:41 +0100 Subject: [PATCH] cover: Use native coverage if supported Update the `cover` tool to use the native coverage feature of the runtime system. Currently at least, the runtime system only supports coverage through the JIT. TO DO: * When running on multiple nodes, only use the native coverage functionality if all connected Erlang nodes support it. * When asked to compile a module and that module is already loaded with line counters enabled, don't compile or load the module. That would support running cover on modules that are not possible to reload (such as many modules in Kernel or even the preloaded modules for Erts). --- lib/tools/src/cover.erl | 141 ++++++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 41 deletions(-) diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 6ed24c833316..42b9ec897a18 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1268,7 +1268,7 @@ do_start_nodes(Nodes, State) -> {_LoadedModules,Compiled} = get_compiled_still_loaded(State#main_state.nodes, State#main_state.compiled), - remote_load_compiled(StartedNodes,Compiled), + remote_load_compiled(StartedNodes, Compiled, State), State1 = State#main_state{nodes = State#main_state.nodes ++ StartedNodes, @@ -1317,7 +1317,7 @@ sync_compiled(Node,State) -> remote_unload([Node],Unload), Load = [L || L <- Compiled, false == lists:member(L,RemoteCompiled)], - remote_load_compiled([Node],Load), + remote_load_compiled([Node], Load, State), State#main_state{compiled=Compiled, nodes=[Node|Nodes]} end, State1#main_state{lost_nodes=Lost--[Node]}. @@ -1326,8 +1326,14 @@ sync_compiled(Node,State) -> %% We do it ?MAX_MODS modules at a time so that we don't %% run out of memory on the cover_server node. -define(MAX_MODS, 10). -remote_load_compiled(Nodes,Compiled) -> - remote_load_compiled(Nodes, Compiled, [], 0). +remote_load_compiled(Nodes, Compiled, #main_state{local_only=LocalOnly}) -> + case LocalOnly of + true -> + ok; + false -> + remote_load_compiled(Nodes, Compiled, [], 0) + end. + remote_load_compiled(_Nodes, [], [], _ModNum) -> ok; remote_load_compiled(Nodes, Compiled, Acc, ModNum) @@ -1639,7 +1645,7 @@ do_compile_beams(ModsAndFiles, State) -> end, ModsAndFiles), Compiled = [{M,F} || {ok,M,F} <- Result0], - remote_load_compiled(State#main_state.nodes,Compiled), + remote_load_compiled(State#main_state.nodes, Compiled, State), fix_state_and_result(Result0,State,[]). do_compile_beam(Module,BeamFile0,State) -> @@ -1680,7 +1686,7 @@ do_compile(Files, Options, State) -> end, Files), Compiled = [{M,F} || {ok,M,F} <- Result0], - remote_load_compiled(State#main_state.nodes,Compiled), + remote_load_compiled(State#main_state.nodes, Compiled, State), fix_state_and_result(Result0,State,[]). do_compile1(File, Options, LocalOnly) -> @@ -1756,7 +1762,9 @@ do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile,LocalOnly) -> %% Compile and load the result. %% It's necessary to check the result of loading since it may %% fail, for example if Module resides in a sticky directory. - Options = SourceInfo ++ UserOptions, + Options0 = SourceInfo ++ UserOptions, + Options = [report_errors,force_line_counters|Options0], + {ok, Module, Binary} = compile:forms(Forms, Options), case code:load_binary(Module, ?TAG, Binary) of @@ -1798,7 +1806,7 @@ get_compile_info(Module, Beam) -> end. transform(Code, Module, _MainFile, LocalOnly) -> - {ok, MungedForms0,InitInfo} = sys_coverage:cover_transform(Code, fun counter_index/5), + {ok,MungedForms0,InitInfo} = sys_coverage:cover_transform(Code, fun counter_index/5), MungedForms = patch_code(Module, MungedForms0, LocalOnly), {MungedForms,InitInfo}. @@ -1832,10 +1840,15 @@ counter_index(Mod, F, A, C, Line) -> %% Create the counter array and store as a persistent term. maybe_create_counters(Mod, true) -> - Cref = create_counters(Mod), - Key = {?MODULE,Mod}, - persistent_term:put(Key, Cref), - ok; + case has_native_coverage() of + false -> + Cref = create_counters(Mod), + Key = {?MODULE,Mod}, + persistent_term:put(Key, Cref), + ok; + true -> + ok + end; maybe_create_counters(_Mod, false) -> ok. @@ -1846,14 +1859,20 @@ create_counters(Mod) -> ets:insert(?COVER_MAPPING_TABLE, {{counters,Mod},Cref}), Cref. -patch_code(Mod, Forms, false) -> - A = erl_anno:new(0), - AbstrKey = {tuple,A,[{atom,A,?MODULE},{atom,A,Mod}]}, - patch_code1(Forms, {distributed,AbstrKey}); -patch_code(Mod, Forms, true) -> - Cref = create_counters(Mod), - AbstrCref = cid_to_abstract(Cref), - patch_code1(Forms, {local_only,AbstrCref}). +patch_code(Mod, Forms, Local) -> + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + Forms; + false when Local =:= false -> + A = erl_anno:new(0), + AbstrKey = {tuple,A,[{atom,A,?MODULE},{atom,A,Mod}]}, + patch_code1(Forms, {distributed,AbstrKey}); + false when Local =:= true -> + Cref = create_counters(Mod), + AbstrCref = cid_to_abstract(Cref), + patch_code1(Forms, {local_only,AbstrCref}) + end. %% Go through the abstract code and replace 'executable_line' forms %% with the actual code to increment the counters. @@ -1907,30 +1926,37 @@ send_counters(Mod, CollectorPid) -> %% Called on the main node. Collect the counters and consolidate %% them into the collection table. Also zero the counters. move_counters(Mod) -> - move_counters(Mod, fun insert_in_collection_table/1). + Process = fun insert_in_collection_table/1, + move_counters(Mod, Process). move_counters(Mod, Process) -> + Move = case has_native_coverage() of + true -> + native_move(Mod); + false -> + standard_move(Mod) + end, Pattern = {#bump{module=Mod,_='_'},'_'}, Matches = ets:match_object(?COVER_MAPPING_TABLE, Pattern, ?CHUNK_SIZE), - Cref = get_counters_ref(Mod), - move_counters1(Matches, Cref, Process). + move_counters1(Matches, Move, Process). -move_counters1({Mappings,Continuation}, Cref, Process) -> - Move = fun({Key,Index}) -> - Count = counters:get(Cref, Index), - ok = counters:sub(Cref, Index, Count), - {Key,Count} - end, - Process(lists:map(Move, Mappings)), - move_counters1(ets:match_object(Continuation), Cref, Process); -move_counters1('$end_of_table', _Cref, _Process) -> +move_counters1({Mappings,Continuation}, Move, Process) -> + Moved = [Move(Item) || Item <- Mappings], + Process(Moved), + move_counters1(ets:match_object(Continuation), Move, Process); +move_counters1('$end_of_table', _Move, _Process) -> ok. counters_mapping_table(Mod) -> Mapping = counters_mapping(Mod), - Cref = get_counters_ref(Mod), - #{size:=Size} = counters:info(Cref), - [{Mod,Size}|Mapping]. + case has_native_coverage() of + false -> + Cref = get_counters_ref(Mod), + #{size:=Size} = counters:info(Cref), + [{Mod,Size}|Mapping]; + true -> + Mapping + end. get_counters_ref(Mod) -> ets:lookup_element(?COVER_MAPPING_TABLE, {counters,Mod}, 2). @@ -1946,14 +1972,44 @@ clear_counters(Mod) -> _ = ets:match_delete(?COVER_MAPPING_TABLE, Pattern), ok. +standard_move(Mod) -> + Cref = get_counters_ref(Mod), + fun({Key,Index}) -> + Count = counters:get(Cref, Index), + ok = counters:sub(Cref, Index, Count), + {Key,Count} + end. + +native_move(Mod) -> + Coverage = maps:from_list(erlang:get_line_coverage(Mod)), + _ = erlang:reset_coverage(Mod), + fun({#bump{line=Line}=Key,_Index}) -> + case Coverage of + #{Line := false} -> + {Key,0}; + #{Line := true} -> + {Key,1}; + #{Line := N} when is_integer(N), N >= 0 -> + {Key,N}; + #{} -> + {Key,0} + end + end. + %% Reset counters (set counters to 0). reset_counters(Mod) -> - Pattern = {#bump{module=Mod,_='_'},'$1'}, - MatchSpec = [{Pattern,[],['$1']}], - Matches = ets:select(?COVER_MAPPING_TABLE, - MatchSpec, ?CHUNK_SIZE), - Cref = get_counters_ref(Mod), - reset_counters1(Matches, Cref). + case has_native_coverage() of + true -> + _ = catch erlang:reset_coverage(Mod), + ok; + false -> + Pattern = {#bump{module=Mod,_='_'},'$1'}, + MatchSpec = [{Pattern,[],['$1']}], + Matches = ets:select(?COVER_MAPPING_TABLE, + MatchSpec, ?CHUNK_SIZE), + Cref = get_counters_ref(Mod), + reset_counters1(Matches, Cref) + end. reset_counters1({Indices,Continuation}, Cref) -> _ = [counters:put(Cref, N, 0) || N <- Indices], @@ -2665,3 +2721,6 @@ html_encoding(latin1) -> "iso-8859-1"; html_encoding(utf8) -> "utf-8". + +has_native_coverage() -> + erlang:system_info(coverage_support).