File 2361-compiler-Add-support-for-coverage.patch of Package erlang

From d8ad1b0390a7678387d63f0476058420290336fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Fri, 3 Nov 2023 09:42:39 +0100
Subject: [PATCH 1/4] compiler: Add support for coverage

Add the new compiler pass `sys_coverage`. It can be invoked by giving
the compiler the `line_coverage`. It will emit `executable_line`
BEAM instructions on each executable line.

There is also an `sys_coverage:cover_transform/2` function, which is
useful for `cover`.
---
 .gitignore                                    |   1 +
 erts/emulator/beam/emu/ops.tab                |   2 +
 .../emulator/beam/jit/arm/beam_asm_module.cpp |   3 +
 erts/emulator/beam/jit/arm/ops.tab            |   2 +
 .../emulator/beam/jit/x86/beam_asm_module.cpp |   3 +
 erts/emulator/beam/jit/x86/ops.tab            |   2 +
 lib/compiler/doc/src/compile.xml              |  33 +-
 lib/compiler/src/Makefile                     |   1 +
 lib/compiler/src/beam_asm.erl                 |  29 +-
 lib/compiler/src/beam_block.erl               |   1 +
 lib/compiler/src/beam_dict.erl                |  33 +-
 lib/compiler/src/beam_disasm.erl              |   7 +
 lib/compiler/src/beam_flatten.erl             |   3 +-
 lib/compiler/src/beam_ssa.erl                 |   2 +-
 lib/compiler/src/beam_ssa_alias.erl           |   2 +
 lib/compiler/src/beam_ssa_codegen.erl         |   4 +
 lib/compiler/src/beam_ssa_opt.erl             |   8 +
 lib/compiler/src/beam_ssa_pre_codegen.erl     |   1 +
 lib/compiler/src/beam_ssa_share.erl           |  31 +-
 lib/compiler/src/beam_validator.erl           |   2 +
 lib/compiler/src/compile.erl                  |   2 +
 lib/compiler/src/compiler.app.src             |   1 +
 lib/compiler/src/genop.tab                    |   6 +
 lib/compiler/src/sys_coverage.erl             | 579 ++++++++++++++++++
 lib/compiler/src/v3_core.erl                  |   4 +
 lib/compiler/test/Makefile                    |  12 +-
 lib/compiler/test/bs_bincomp_SUITE.erl        |   2 +
 lib/compiler/test/test_lib.erl                |   5 +-
 lib/stdlib/src/erl_expand_records.erl         |   4 +-
 lib/stdlib/src/erl_lint.erl                   |   2 +
 lib/stdlib/src/erl_pp.erl                     |   2 +
 31 files changed, 752 insertions(+), 37 deletions(-)
 create mode 100644 lib/compiler/src/sys_coverage.erl

diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab
index 4d227cc89b..2b08dd60a9 100644
--- a/erts/emulator/beam/emu/ops.tab
+++ b/erts/emulator/beam/emu/ops.tab
@@ -95,6 +95,8 @@ move S X0=x==0 | line Loc => line Loc | move S X0
 line n => _
 line I
 
+executable_line Line => _
+
 # For the JIT, the init_yregs/1 instruction allows generation of better code.
 # For the BEAM interpreter, though, it will probably be more efficient to
 # translate all uses of init_yregs/1 back to the instructions that the compiler
diff --git a/erts/emulator/beam/jit/arm/beam_asm_module.cpp b/erts/emulator/beam/jit/arm/beam_asm_module.cpp
index c66c10e6bf..c9eaff19d2 100644
--- a/erts/emulator/beam/jit/arm/beam_asm_module.cpp
+++ b/erts/emulator/beam/jit/arm/beam_asm_module.cpp
@@ -451,6 +451,9 @@ void BeamModuleAssembler::emit_func_line(const ArgWord &Loc) {
 void BeamModuleAssembler::emit_empty_func_line() {
 }
 
+void BeamModuleAssembler::emit_executable_line(const ArgWord &Loc) {
+}
+
 /*
  * Here follows stubs for instructions that should never be called.
  */
diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab
index c5a7da3142..f3c99bac9f 100644
--- a/erts/emulator/beam/jit/arm/ops.tab
+++ b/erts/emulator/beam/jit/arm/ops.tab
@@ -86,6 +86,8 @@ func_line I
 line n => _
 line I
 
+executable_line I
+
 allocate t t
 allocate_heap t I t
 
diff --git a/erts/emulator/beam/jit/x86/beam_asm_module.cpp b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
index 7eb4e2d6be..cc4a4b7e74 100644
--- a/erts/emulator/beam/jit/x86/beam_asm_module.cpp
+++ b/erts/emulator/beam/jit/x86/beam_asm_module.cpp
@@ -383,6 +383,9 @@ void BeamModuleAssembler::emit_func_line(const ArgWord &Loc) {
 void BeamModuleAssembler::emit_empty_func_line() {
 }
 
+void BeamModuleAssembler::emit_executable_line(const ArgWord &Loc) {
+}
+
 /*
  * Here follows stubs for instructions that should never be called.
  */
diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab
index 39b9f32ef3..b835114b2a 100644
--- a/erts/emulator/beam/jit/x86/ops.tab
+++ b/erts/emulator/beam/jit/x86/ops.tab
@@ -86,6 +86,8 @@ func_line I
 line n => _
 line I
 
+executable_line I
+
 allocate t t
 allocate_heap t I t
 
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index a623281eb7..d7c5e34dd2 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -620,7 +620,38 @@ module.beam: module.erl \
             option is internal to the compiler and can be changed or
             removed at any time without prior warning.</p>
           </item>
-	</taglist>
+
+          <tag><c>line_coverage</c></tag>
+          <item>
+            <p><marker id="line_coverage"/>Instrument the compiled
+            code for line coverage by inserting an
+            <c>executable_line</c> instruction for each executable
+            line in the source code. By default, this instruction will
+            be ignored when loading the code.</p> <p>To activate the
+            <c>executable_line</c> instructions, the runtime system
+            must be started with the option <seecom
+            marker="erts:erl#+JPcover">+JPcover</seecom> to enable a
+            coverage mode. Alternatively, <seemfa
+            marker="kernel:code#set_coverage_mode/1">code:set_coverage_mode/1</seemfa>
+            can be used to set a coverage mode before
+            loading the code.</p>
+            <p>The coverage information gathered by the instrumented
+            code can be retrieved by calling <seemfa
+            marker="kernel:code#get_coverage/2">code:get_coverage(line, Module)</seemfa>.</p>
+          </item>
+
+	  <tag><c>force_line_counters</c></tag>
+          <item>
+            <p><marker id="force_line_counters"/> When combined with
+            option <c>line_coverage</c>, this module will be loaded in
+            the <c>line_counter</c> coverage mode, regardless of the
+            current <seemfa
+            marker="kernel:code#get_coverage_mode/0">coverage
+            mode</seemfa> in the runtime system. This option is used
+            by <seeerl marker="tools:cover">cover</seeerl> to load
+            cover-compiled code.</p>
+          </item>
+        </taglist>
 
         <p>If warnings are turned on (option <c>report_warnings</c>
           described earlier), the following options control what type of
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index 7e59241429..a33d14f2d5 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -100,6 +100,7 @@ MODULES =  \
 	sys_core_fold_lists \
 	sys_core_inline \
 	sys_core_prepare \
+	sys_coverage \
 	sys_messages \
 	sys_pre_attributes \
 	v3_core \
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index 277c433013..0186ab3e04 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -55,6 +55,10 @@
 -type module_code() ::
         {module(),[_],[_],[asm_function()],pos_integer()}.
 
+%% Flags for the line table.
+-define(BEAMFILE_EXECUTABLE_LINE, 1).
+-define(BEAMFILE_FORCE_LINE_COUNTERS, 2).
+
 -spec module(module_code(), [{binary(), binary()}], [{atom(),term()}], [compile:option()]) ->
                     {'ok',binary()}.
 
@@ -180,7 +184,7 @@ build_file(Code, Attr, Dict, NumLabels, NumFuncs, ExtraChunks0, CompileInfo, Com
 		   end,
 
     %% Create the line chunk.
-    LineChunk = chunk(<<"Line">>, build_line_table(Dict)),
+    LineChunk = chunk(<<"Line">>, build_line_table(Dict, CompilerOpts)),
 
     %% Create the type table chunk.
     {NumTypes, TypeTab} = beam_dict:type_table(Dict),
@@ -282,8 +286,8 @@ build_attributes(Attr, Compile, MD5) ->
     CompileBinary = term_to_binary([{version,?COMPILER_VSN}|Compile]),
     {AttrBinary,CompileBinary}.
 
-build_line_table(Dict) ->
-    {NumLineInstrs,NumFnames0,Fnames0,NumLines,Lines0} =
+build_line_table(Dict, Options) ->
+    {NumLineInstrs,NumFnames0,Fnames0,NumLines,Lines0,ExecLine} =
 	beam_dict:line_table(Dict),
     NumFnames = NumFnames0 - 1,
     [_|Fnames1] = Fnames0,
@@ -292,10 +296,20 @@ build_line_table(Dict) ->
     Lines1 = encode_line_items(Lines0, 0),
     Lines = iolist_to_binary(Lines1),
     Ver = 0,
-    Bits = 0,
+    Bits = line_bits(ExecLine, Options),
     <<Ver:32,Bits:32,NumLineInstrs:32,NumLines:32,NumFnames:32,
      Lines/binary,Fnames/binary>>.
 
+line_bits(ExecLine, Options) ->
+        case member(force_line_counters, Options) of
+            true ->
+                ?BEAMFILE_FORCE_LINE_COUNTERS bor ?BEAMFILE_EXECUTABLE_LINE;
+            false when ExecLine =:= true ->
+                ?BEAMFILE_EXECUTABLE_LINE;
+            false ->
+                0
+        end.
+
 %% encode_line_items([{FnameIndex,Line}], PrevFnameIndex)
 %%  Encode the line items compactly. Tag the FnameIndex with
 %%  an 'a' tag (atom) and only include it when it has changed.
@@ -347,9 +361,12 @@ bif_type(_, 2)      -> bif2.
 
 make_op({'%',_}, Dict) ->
     {[],Dict};
-make_op({line,Location}, Dict0) ->
-    {Index,Dict} = beam_dict:line(Location, Dict0),
+make_op({line=Op,Location}, Dict0) ->
+    {Index,Dict} = beam_dict:line(Location, Dict0, Op),
     encode_op(line, [Index], Dict);
+make_op({executable_line=Op,Location}, Dict0) ->
+    {Index,Dict} = beam_dict:line(Location, Dict0, Op),
+    encode_op(executable_line, [Index], Dict);
 make_op({bif, Bif, {f,_}, [], Dest}, Dict) ->
     %% BIFs without arguments cannot fail.
     encode_op(bif0, [{extfunc, erlang, Bif, 0}, Dest], Dict);
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index b6d424d09c..9cd6d93783 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -169,6 +169,7 @@ collect({put_map,{f,0},Op,S,D,R,{list,Puts}}) ->
     {set,[D],[S|Puts],{alloc,R,{put_map,Op,{f,0}}}};
 collect({fmove,S,D})         -> {set,[D],[S],fmove};
 collect({fconv,S,D})         -> {set,[D],[S],fconv};
+collect({executable_line,Line}) -> {set,[],[],{executable_line,Line}};
 collect(_)                   -> error.
 
 %% embed_lines([Instruction]) -> [Instruction]
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index 36bdb01df5..dbc74c1518 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -23,7 +23,7 @@
 
 -export([new/0,opcode/2,highest_opcode/1,
 	 atom/2,local/4,export/4,import/4,
-	 string/2,lambda/3,literal/2,line/2,fname/2,type/2,
+	 string/2,lambda/3,literal/2,line/3,fname/2,type/2,
 	 atom_table/1,local_table/1,export_table/1,import_table/1,
 	 string_table/1,lambda_table/1,literal_table/1,
 	 line_table/1,type_table/1]).
@@ -58,6 +58,7 @@
          fnames = #{}               :: fname_tab(),
          lines = #{}                :: line_tab(),
          num_lines = 0              :: non_neg_integer(), %Number of line instructions
+         exec_line = false          :: boolean(),
          next_import = 0            :: non_neg_integer(),
          string_offset = 0          :: non_neg_integer(),
          next_literal = 0           :: non_neg_integer(),
@@ -201,23 +202,29 @@ literal1(Key, #asm{literals=Tab0,next_literal=NextIndex}=Dict) ->
 
 %% Returns the index for a line instruction (adding information
 %% to the location information table).
--spec line(list(), bdict()) -> {non_neg_integer(), bdict()}.
+-spec line(list(), bdict(), 'line' | 'executable_line') ->
+          {non_neg_integer(), bdict()}.
 
-line([], #asm{num_lines=N}=Dict) ->
+line([], #asm{num_lines=N}=Dict, Instr) when is_atom(Instr) ->
     %% No location available. Return the special pre-defined
     %% index 0.
     {0,Dict#asm{num_lines=N+1}};
-line([{location,Name,Line}|_], #asm{lines=Lines,num_lines=N}=Dict0) ->
+line([{location,Name,Line}|_], #asm{lines=Lines,num_lines=N,
+                                    exec_line=ExecLine0}=Dict0, Instr)
+  when is_atom(Instr) ->
     {FnameIndex,Dict1} = fname(Name, Dict0),
     Key = {FnameIndex,Line},
+    ExecLine = ExecLine0 or (Instr =:= executable_line),
     case Lines of
-        #{Key := Index} -> {Index,Dict1#asm{num_lines=N+1}};
+        #{Key := Index} ->
+            {Index,Dict1#asm{num_lines=N+1,exec_line=ExecLine}};
         _ ->
-	    Index = maps:size(Lines) + 1,
-            {Index, Dict1#asm{lines=Lines#{Key=>Index},num_lines=N+1}}
+            Index = map_size(Lines) + 1,
+            {Index, Dict1#asm{lines=Lines#{Key=>Index},num_lines=N+1,
+                              exec_line=ExecLine}}
     end;
-line([_|T], #asm{}=Dict) ->
-    line(T, Dict).
+line([_|T], #asm{}=Dict, Instr) ->
+    line(T, Dict, Instr).
 
 -spec fname(nonempty_string(), bdict()) ->
                    {non_neg_integer(), bdict()}.
@@ -337,16 +344,18 @@ build_type_table([], Acc) ->
 -spec line_table(bdict()) ->
     {non_neg_integer(),				%Number of line instructions.
      non_neg_integer(),[string()],
-     non_neg_integer(),[{non_neg_integer(),non_neg_integer()}]}.
+     non_neg_integer(),[{non_neg_integer(),non_neg_integer()}],
+     boolean()}.
 
-line_table(#asm{fnames=Fnames0,lines=Lines0,num_lines=NumLineInstrs}) ->
+line_table(#asm{fnames=Fnames0,lines=Lines0,
+                num_lines=NumLineInstrs,exec_line=ExecLine}) ->
     NumFnames = maps:size(Fnames0),
     Fnames1 = lists:keysort(2, maps:to_list(Fnames0)),
     Fnames = [Name || {Name,_} <- Fnames1],
     NumLines = maps:size(Lines0),
     Lines1 = lists:keysort(2, maps:to_list(Lines0)),
     Lines = [L || {L,_} <- Lines1],
-    {NumLineInstrs,NumFnames,Fnames,NumLines,Lines}.
+    {NumLineInstrs,NumFnames,Fnames,NumLines,Lines,ExecLine}.
 
 %% Search for binary string Str in the binary string pool Pool.
 %%    old_string(Str, Pool) -> none | Index
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index 35ba0ba82d..06064b1469 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1303,6 +1303,13 @@ resolve_inst({bs_match,[{Fail,Ctx,{z,1},{u,_},Args}]},_,_,_) ->
     Commands = resolve_bs_match_commands(List),
     {bs_match,Fail,Ctx,{commands,Commands}};
 
+%%
+%% OTP 27.
+%%
+
+resolve_inst({executable_line,[Index]},_,_,_) ->
+    {line,resolve_arg(Index)};
+
 %%
 %% Catches instructions that are not yet handled.
 %%
diff --git a/lib/compiler/src/beam_flatten.erl b/lib/compiler/src/beam_flatten.erl
index c8298d5e6f..9a29b1ad50 100644
--- a/lib/compiler/src/beam_flatten.erl
+++ b/lib/compiler/src/beam_flatten.erl
@@ -62,7 +62,8 @@ norm({set,[D],[S],get_tl})        -> {get_tl,S,D};
 norm({set,[D],[S|Puts],{alloc,R,{put_map,Op,F}}}) ->
     {put_map,F,Op,S,D,R,{list,Puts}};
 norm({set,[],[],remove_message})   -> remove_message;
-norm({set,[],[],{line,_}=Line}) -> Line.
+norm({set,[],[],{line,_}=Line}) -> Line;
+norm({set,[],[],{executable_line,_}=Line}) -> Line.
 
 norm_allocate({_Zero,nostack,Nh,[]}, Regs) ->
     [{test_heap,Nh,Regs}];
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index db35a2fa41..4650def062 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -109,7 +109,7 @@
                    'bs_match' | 'bs_start_match' | 'bs_test_tail' |
                    'build_stacktrace' |
                    'call' | 'catch_end' |
-                   'extract' |
+                   'executable_line' | 'extract' |
                    'get_hd' | 'get_map_element' | 'get_tl' | 'get_tuple_element' |
                    'has_map_field' |
                    'is_nonempty_list' | 'is_tagged_tuple' |
diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl
index 6dd4b92e5d..554fbdcd68 100644
--- a/lib/compiler/src/beam_ssa_alias.erl
+++ b/lib/compiler/src/beam_ssa_alias.erl
@@ -512,6 +512,8 @@ aa_is([I=#b_set{dst=Dst,op=Op,args=Args,anno=Anno0}|Is], SS0, AAS0) ->
                 {SS1, AAS0};
             bs_test_tail ->
                 {SS1, AAS0};
+            executable_line ->
+                {SS1, AAS0};
             has_map_field ->
                 {SS1, AAS0};
             is_nonempty_list ->
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index 0399d358de..739180d99c 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -389,6 +389,7 @@ classify_heap_need(build_stacktrace) -> gc;
 classify_heap_need(call) -> gc;
 classify_heap_need(catch_end) -> gc;
 classify_heap_need(copy) -> neutral;
+classify_heap_need(executable_line) -> neutral;
 classify_heap_need(extract) -> gc;
 classify_heap_need(get_hd) -> neutral;
 classify_heap_need(get_map_element) -> neutral;
@@ -1809,6 +1810,9 @@ cg_instr(bs_get_tail, [Src], Dst, Set) ->
 cg_instr(bs_get_position, [Ctx], Dst, Set) ->
     Live = get_live(Set),
     [{bs_get_position,Ctx,Dst,Live}];
+cg_instr(executable_line, [], _Dst, #cg_set{anno=Anno}) ->
+    {line,Location} = line(Anno),
+    [{executable_line,Location}];
 cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
     Live = get_live(Set),
     [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index 06c1e79f20..620dcb941a 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -488,6 +488,14 @@ merge_updates_bs([{LblA,
     %% Note that we retain the first update_record in case it's used elsewhere,
     %% it's too rare to warrant special handling here.
     [{LblA, BlkA}, {LblB, BlkB#b_blk{is=[Update]}}| merge_updates_bs(Bs)];
+merge_updates_bs([{LblA,
+                   #b_blk{is=[#b_set{op=update_record}]=IsA,
+                          last=#b_br{bool=#b_literal{val=true},
+                                     succ=LblB}}=BlkA},
+                  {LblB,
+                   #b_blk{is=[#b_set{op=executable_line}]=IsB}=BlkB} | Bs0]) ->
+    Bs = [{LblB,BlkB#b_blk{is=IsA}} | Bs0],
+    [{LblA,BlkA#b_blk{is=IsB}} | merge_updates_bs(Bs)];
 merge_updates_bs([{Lbl, Blk} | Bs]) ->
     [{Lbl, Blk} | merge_updates_bs(Bs)];
 merge_updates_bs([]) ->
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 8ca16374d0..572d771e98 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -2604,6 +2604,7 @@ use_zreg(bs_checked_skip) -> yes;
 use_zreg(bs_ensure) -> yes;
 use_zreg(bs_match_string) -> yes;
 use_zreg(bs_set_position) -> yes;
+use_zreg(executable_line) -> yes;
 use_zreg(kill_try_tag) -> yes;
 use_zreg(landingpad) -> yes;
 use_zreg(recv_marker_bind) -> yes;
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
index 13b6038868..8ef50eede2 100644
--- a/lib/compiler/src/beam_ssa_share.erl
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -284,8 +284,9 @@ canonical_block({L,VarMap0}, Blocks) ->
 %%    * Variables defined in the instruction sequence are replaced with
 %%    {var,0}, {var,1}, and so on. Free variables are not changed.
 %%
-%%    * `location` annotations that would produce a `line` instruction are
-%%    kept. All other annotations are cleared.
+%%    * `location` annotations that would produce `line` or
+%%    `executable_line` instructions are kept. All other annotations
+%%    are cleared.
 %%
 %%    * Instructions are repackaged into tuples instead of into the
 %%    usual records. The main reason is to avoid violating the types for
@@ -300,17 +301,21 @@ canonical_is([#b_set{op=Op,dst=Dst,args=Args0}=I|Is], VarMap0, Acc) ->
     Args = [canonical_arg(Arg, VarMap0) || Arg <- Args0],
     Var = {var,map_size(VarMap0)},
     VarMap = VarMap0#{Dst=>Var},
-    LineAnno = case Op of
-                   bs_match ->
-                       %% The location annotation for a bs_match instruction
-                       %% is only used in warnings, never to emit a `line`
-                       %% instruction. Therefore, it should not be included.
-                       [];
-                   _ ->
-                       %% The location annotation will be used in a `line`
-                       %% instruction. It must be included.
-                       beam_ssa:get_anno(location, I, none)
-               end,
+    LineAnno =
+        case {Op,Is} of
+            {executable_line, _} ->
+                %% The location annotation will be used in a
+                %% `executable_line` instruction.
+                beam_ssa:get_anno(location, I, none);
+            {_, [#b_set{op={succeeded,body},args=[Dst]}|_]} ->
+                %% The location annotation will be used in a `line`
+                %% instruction.
+                beam_ssa:get_anno(location, I, none);
+            {_, _} ->
+                %% The location annotation will not be included in
+                %% any BEAM instruction.
+                []
+        end,
     canonical_is(Is, VarMap, {Op,LineAnno,Var,Args,Acc});
 canonical_is([#b_ret{arg=Arg}], VarMap, Acc) ->
     {{ret,canonical_arg(Arg, VarMap),Acc},VarMap};
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index ead9c12052..98bc729b18 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -372,6 +372,8 @@ vi({'%',_}, Vst) ->
     Vst;
 vi({line,_}, Vst) ->
     Vst;
+vi({executable_line,_}, Vst) ->
+    Vst;
 vi(nif_start, Vst) ->
     Vst;
 %%
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 33c47d68a7..d4e693aee0 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -830,6 +830,8 @@ abstr_passes(AbstrStatus) ->
 
          {delay,[{iff,debug_info,?pass(save_abstract_code)}]},
 
+         {iff,line_coverage,{pass,sys_coverage}},
+
          ?pass(expand_records),
          {iff,'dexp',{listing,"expand"}},
          {iff,'E',?pass(legalize_vars)},
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index 90448a67a0..71588c0826 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -75,6 +75,7 @@
 	     sys_core_fold_lists,
 	     sys_core_inline,
 	     sys_core_prepare,
+             sys_coverage,
 	     sys_messages,
 	     sys_pre_attributes,
 	     v3_core,
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index f2d50f3bc2..aec38bb54c 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -689,3 +689,9 @@ BEAM_FORMAT_NUMBER=0
 ##       * {get_tail,Live,Unit,Dst}
 ##       * {'=:=',Live,Size,Value}.
 182: bs_match/3
+
+# OTP 27
+
+## @spec executable_line Location
+## @doc  Provide location for an executable line.
+183: executable_line/1
diff --git a/lib/compiler/src/sys_coverage.erl b/lib/compiler/src/sys_coverage.erl
new file mode 100644
index 0000000000..994fa7b016
--- /dev/null
+++ b/lib/compiler/src/sys_coverage.erl
@@ -0,0 +1,579 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Purpose: Instrument abstract code for coverage.
+
+-module(sys_coverage).
+-export([module/2,cover_transform/2]).
+-import(lists, [member/2,reverse/1,reverse/2]).
+
+-type attribute() :: atom().
+-type form()      :: {function, integer(), atom(), arity(), _}
+                   | {attribute, integer(), attribute(), _}.
+-type index_fun() :: fun((module(), atom(), arity(),
+                          non_neg_integer(), non_neg_integer()) ->
+                                non_neg_integer()).
+
+-spec module([form()], [compile:option()]) ->
+        {'ok',[form()]}.
+
+module(Forms0, _Opts) when is_list(Forms0) ->
+    IndexFun = fun(_, _, _, _, _) -> 0 end,
+    transform(Forms0, IndexFun).
+
+%% Undocumented helper function for the `cover` module.
+-spec cover_transform([form()], index_fun()) ->
+          {'ok',[form()]}.
+
+cover_transform(Forms, IndexFun) when is_list(Forms),
+                                      is_function(IndexFun, 5) ->
+    transform(Forms, IndexFun).
+
+%%%
+%%% Local functions.
+%%%
+
+%% Line doesn't matter.
+-define(BLOCK(Expr), {block,erl_anno:new(0),[Expr]}).
+-define(BLOCK1(Expr),
+        if
+            element(1, Expr) =:= block ->
+                Expr;
+            true ->
+                ?BLOCK(Expr)
+        end).
+-define(EXECUTABLE_LINE, executable_line).
+
+-record(vars,
+        {module=[]      :: module() | [],
+         function=none  :: atom(),
+         arity=0        :: arity(),
+         clause=0       :: non_neg_integer(),
+         lines=[]       :: [non_neg_integer()],
+         bump_lines=[]  :: [non_neg_integer()],
+         in_guard=false :: boolean(),
+         index_fun      :: index_fun()
+        }).
+
+transform(Code, IndexFun) ->
+    Vars = #vars{index_fun=IndexFun},
+    transform(Code, [], Vars, none, on).
+
+transform([Form0|Forms], MungedForms, Vars0, MainFile0, Switch0) ->
+    Form = expand(Form0),
+    case munge(Form, Vars0, MainFile0, Switch0) of
+        ignore ->
+            transform(Forms, MungedForms, Vars0, MainFile0, Switch0);
+        {MungedForm, Vars, Switch} ->
+            transform(Forms, [MungedForm|MungedForms], Vars, MainFile0, Switch);
+        {MungedForm,Vars,MainFile,Switch} ->
+            transform(Forms, [MungedForm|MungedForms], Vars, MainFile, Switch)
+    end;
+transform([], MungedForms, _Vars, _, _) ->
+    {ok, reverse(MungedForms)}.
+
+%% Expand short-circuit Boolean expressions.
+expand(Expr0) ->
+    AllVars = sets:from_list(ordsets:to_list(vars([], Expr0)), [{version,2}]),
+    {Expr,_} = expand(Expr0, AllVars, 1),
+    Expr.
+
+expand({clause,Anno,Pattern,Guards,Body}, Vs, N) ->
+    %% We must not expand andalso/orelse in guards.
+    {ExpandedBody,N2} = expand(Body, Vs, N),
+    {{clause,Anno,Pattern,Guards,ExpandedBody},N2};
+expand({lc,Anno,Expr,Qs}, Vs, N) ->
+    {ExpandedExpr,N2} = expand(Expr, Vs, N),
+    {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2),
+    {{lc,Anno,ExpandedExpr,ExpandedQs},N3};
+expand({bc,Anno,Expr,Qs}, Vs, N) ->
+    {ExpandedExpr,N2} = expand(Expr, Vs, N),
+    {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2),
+    {{bc,Anno,ExpandedExpr,ExpandedQs},N3};
+expand({mc,Anno,Expr,Qs}, Vs, N) ->
+    {ExpandedExpr,N2} = expand(Expr, Vs, N),
+    {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2),
+    {{mc,Anno,ExpandedExpr,ExpandedQs},N3};
+expand({op,_Anno,'andalso',ExprL,ExprR}, Vs, N) ->
+    {ExpandedExprL,N2} = expand(ExprL, Vs, N),
+    {ExpandedExprR,N3} = expand(ExprR, Vs, N2),
+    Anno = element(2, ExpandedExprL),
+    {bool_switch(ExpandedExprL,
+                 ExpandedExprR,
+                 {atom,Anno,false},
+                 Vs, N3),
+     N3 + 1};
+expand({op,_Anno,'orelse',ExprL,ExprR}, Vs, N) ->
+    {ExpandedExprL,N2} = expand(ExprL, Vs, N),
+    {ExpandedExprR,N3} = expand(ExprR, Vs, N2),
+    Anno = element(2, ExpandedExprL),
+    {bool_switch(ExpandedExprL,
+                 {atom,Anno,true},
+                 ExpandedExprR,
+                 Vs, N3),
+     N3 + 1};
+expand(T, Vs, N) when is_tuple(T) ->
+    {TL,N2} = expand(tuple_to_list(T), Vs, N),
+    {list_to_tuple(TL),N2};
+expand([E|Es], Vs, N) ->
+    {E2,N2} = expand(E, Vs, N),
+    {Es2,N3} = expand(Es, Vs, N2),
+    {[E2|Es2],N3};
+expand(T, _Vs, N) ->
+    {T,N}.
+
+expand_qualifiers([Q|Qs], Vs, N) ->
+    {Q2,N2} = case erl_lint:is_guard_test(Q) of
+                  true ->
+                      %% This qualifier is a guard test and will be
+                      %% compiled as such. Don't expand andalso/orelse
+                      %% because that would turn it into a body
+                      %% expression that may raise an exception. Here
+                      %% is an example of a filter where the error
+                      %% behaviour would change:
+                      %%
+                      %%      V == a orelse element(1, V) == a
+                      %%
+                      {Q,N};
+                  false ->
+                      %% A generator or a filter that is not a guard
+                      %% test.
+                      expand(Q, Vs, N)
+              end,
+    {Qs2,N3} = expand_qualifiers(Qs, Vs, N2),
+    {[Q2|Qs2],N3};
+expand_qualifiers([], _Vs, N) ->
+    {[],N}.
+
+vars(A, {var,_,V}) when V =/= '_' ->
+    [V|A];
+vars(A, T) when is_tuple(T) ->
+    vars(A, tuple_to_list(T));
+vars(A, [E|Es]) ->
+    vars(vars(A, E), Es);
+vars(A, _T) ->
+    A.
+
+bool_switch(E, T, F, AllVars, AuxVarN) ->
+    Anno = element(2, E),
+    AuxVar = {var,Anno,aux_var(AllVars, AuxVarN)},
+    {'case',Anno,E,
+     [{clause,Anno,[{atom,Anno,true}],[],[T]},
+      {clause,Anno,[{atom,Anno,false}],[],[F]},
+      %% Mark the next clause as compiler-generated to suppress
+      %% a warning if the case expression is an obvious boolean
+      %% value.
+      {clause,erl_anno:set_generated(true, Anno),[AuxVar],[],
+       [{call,Anno,
+         {remote,Anno,{atom,Anno,erlang},{atom,Anno,error}},
+         [{tuple,Anno,[{atom,Anno,badarg},AuxVar]}]}]}]}.
+
+aux_var(Vars, N) ->
+    Name = list_to_atom(lists:concat(['_', N])),
+    case sets:is_element(Name, Vars) of
+        true -> aux_var(Vars, N + 1);
+        false -> Name
+    end.
+
+%% This code traverses the abstract code, stored as the abstract_code
+%% chunk in the BEAM file, as described in absform(3). The switch is
+%% turned off when we encounter other files than the main file. This
+%% way we will be able to exclude functions defined in include files.
+
+munge({attribute,_,file,{File,_}}=Form, Vars, none, _) ->
+    {Form,Vars,File,on};
+munge({attribute,_,module,Mod}=Form, Vars, MainFile, Switch) when is_atom(Mod) ->
+    {Form,Vars#vars{module=Mod},MainFile,Switch};
+munge({function,Anno,Function,Arity,Clauses}, Vars0, _MainFile, on) ->
+    Vars = Vars0#vars{function=Function,
+                      arity=Arity,
+                      clause=1,
+                      lines=[],
+                      bump_lines=[]},
+    MungedClauses = munge_fun_clauses(Clauses, Vars),
+    {{function,Anno,Function,Arity,MungedClauses},Vars,on};
+munge({attribute,_,file,{MainFile,_}}=Form, Vars, MainFile, _Switch) ->
+    {Form,Vars,on};                     % Switch on transformation!
+munge({attribute,_,file,{_InclFile,_}}=Form, Vars, _MainFile, _Switch) ->
+    {Form,Vars,off};                    % Switch off transformation!
+munge({attribute,_,compile,{parse_transform,_}}, _, _, _) ->
+    %% Don't want to run parse transforms more than once.
+    ignore;
+munge(Form, Vars, _MainFile, Switch) -> % Other attributes and skipped includes.
+    {Form,Vars,Switch}.
+
+munge_fun_clauses([Clause0|Clauses], #vars{clause=ClauseIndex}=Vars0) ->
+    {clause,Anno,Pattern,Guards,Body} = Clause0,
+    {MungedGuards,_} = munge_exprs(Guards, Vars0#vars{in_guard=true}),
+    {MungedBody,_} = munge_body(Body, Vars0),
+    Vars = Vars0#vars{clause=ClauseIndex + 1},
+    Clause = {clause,Anno,Pattern,MungedGuards,MungedBody},
+    [Clause|munge_fun_clauses(Clauses, Vars)];
+munge_fun_clauses([], _Vars) -> [].
+
+%% Munge clauses in case, if, maybe, receive, and try.
+munge_clauses(Clauses, Vars) ->
+    munge_clauses(Clauses, Vars, Vars#vars.lines, []).
+
+munge_clauses([Clause|Clauses], Vars0, Lines0, MClauses) ->
+    {clause,Anno,Pattern,Guards,Body} = Clause,
+    {MungedGuards, _Vars} = munge_exprs(Guards, Vars0#vars{in_guard=true}),
+    {MungedBody, Vars} = munge_body(Body, Vars0),
+    NewBumps = new_bumps(Vars, Vars0),
+    Lines = NewBumps ++ Lines0,
+    munge_clauses(Clauses, Vars#vars{lines=Vars0#vars.lines},
+                  Lines,
+                  [{clause,Anno,Pattern,MungedGuards,MungedBody}|
+                   MClauses]);
+munge_clauses([], Vars, Lines, MungedClauses) ->
+    {reverse(MungedClauses), Vars#vars{lines=Lines}}.
+
+munge_body(Expr, Vars) ->
+    munge_body(Expr, Vars, [], []).
+
+munge_body([E0|Es], Vars0, Acc0, LastExprBumpLines) ->
+    %% Here is the place to add an executable_line instruction.
+    Line = erl_anno:line(element(2, E0)),
+    Lines0 = Vars0#vars.lines,
+    case member(Line, Lines0) of
+	true ->
+            %% There is already a bump at this line.
+	    {E1,Vars1} = munge_expr(E0, Vars0),
+            NewBumps = new_bumps(Vars1, Vars0),
+            BumpLines = [Line|Vars0#vars.bump_lines],
+            Vars2 = Vars1#vars{bump_lines=BumpLines},
+            Acc1 = maybe_fix_last_expr(Acc0, Vars2, LastExprBumpLines),
+            Acc = [E1|Acc1],
+	    munge_body(Es, Vars2, Acc, NewBumps);
+	false ->
+            %% Put a bump at this line.
+            Bump = bump_call(Vars0, Line),
+	    Lines1 = [Line|Lines0],
+	    {E1,Vars1} = munge_expr(E0, Vars0#vars{lines=Lines1}),
+            NewBumps = new_bumps(Vars1, Vars0),
+            BumpLines = subtract(Vars1#vars.bump_lines, NewBumps),
+            Vars2 = Vars1#vars{bump_lines=BumpLines},
+            Acc1 = maybe_fix_last_expr(Acc0, Vars2, LastExprBumpLines),
+            Acc = [E1,Bump|Acc1],
+	    munge_body(Es, Vars2, Acc, NewBumps)
+    end;
+munge_body([], Vars, Acc, _LastExprBumpLines) ->
+    {reverse(Acc), Vars}.
+
+%%% Fix last expression (OTP-8188). A typical example:
+%%%
+%%%  3:   case X of
+%%%  4:       1 -> a; % Bump line 5 after "a" has been evaluated!
+%%%  5:       2 -> b; 3 -> c end, F()
+%%%
+%%% Line 5 wasn't bumped just before "F()" since it was already bumped
+%%% before "b" (and before "c") (one mustn't bump a line more than
+%%% once in a single "evaluation"). The expression "case X ... end" is
+%%% now traversed again ("fixed"), this time adding bumps of line 5
+%%% where appropriate, in this case when X matches 1.
+%%%
+%%% This doesn't solve all problems with expressions on the same line,
+%%% though. 'case' and 'try' are tricky. An example:
+%%%
+%%% 7:    case case X of 1 -> foo(); % ?
+%%% 8:                   2 -> bar() end of a -> 1;
+%%% 9:                                     b -> 2 end.
+%%%
+%%% If X matches 1 and foo() evaluates to a then line 8 should be
+%%% bumped, but not if foo() evaluates to b. In other words, line 8
+%%% cannot be bumped after "foo()" on line 7, so one has to bump line
+%%% 8 before "begin 1 end". But if X matches 2 and bar evaluates to a
+%%% then line 8 would be bumped twice (there has to be a bump before
+%%% "bar()". It is like one would have to have two copies of the inner
+%%% clauses, one for each outer clause. Maybe the munging should be
+%%% done on some of the compiler's "lower level" format.
+%%%
+%%% 'fun' is also problematic since a bump inside the body "shadows"
+%%% the rest of the line.
+
+maybe_fix_last_expr(MungedExprs, Vars, LastExprBumpLines) ->
+    case last_expr_needs_fixing(Vars, LastExprBumpLines) of
+        {yes, Line} ->
+            fix_last_expr(MungedExprs, Line, Vars);
+        no ->
+            MungedExprs
+    end.
+
+last_expr_needs_fixing(Vars, LastExprBumpLines) ->
+    case common_elems(Vars#vars.bump_lines, LastExprBumpLines) of
+        [Line] ->
+            {yes, Line};
+        _ ->
+            no
+    end.
+
+fix_last_expr([MungedExpr|MungedExprs], Line, Vars) ->
+    %% No need to update ?COVER_TABLE.
+    Bump = bump_call(Vars, Line),
+    [fix_expr(MungedExpr, Line, Bump)|MungedExprs].
+
+fix_expr({'if',A,Clauses}, Line, Bump) ->
+    FixedClauses = fix_clauses(Clauses, Line, Bump),
+    {'if',A,FixedClauses};
+fix_expr({'case',A,Expr,Clauses}, Line, Bump) ->
+    FixedExpr = fix_expr(Expr, Line, Bump),
+    FixedClauses = fix_clauses(Clauses, Line, Bump),
+    {'case',A,FixedExpr,FixedClauses};
+fix_expr({'receive',A,Clauses}, Line, Bump) ->
+    FixedClauses = fix_clauses(Clauses, Line, Bump),
+    {'receive',A,FixedClauses};
+fix_expr({'receive',A,Clauses,Expr,Body}, Line, Bump) ->
+    FixedClauses = fix_clauses(Clauses, Line, Bump),
+    FixedExpr = fix_expr(Expr, Line, Bump),
+    FixedBody = fix_expr(Body, Line, Bump),
+    {'receive',A,FixedClauses,FixedExpr,FixedBody};
+fix_expr({'try',A,Exprs,Clauses,CatchClauses,After}, Line, Bump) ->
+    FixedExprs = fix_expr(Exprs, Line, Bump),
+    FixedClauses = fix_clauses(Clauses, Line, Bump),
+    FixedCatchClauses = fix_clauses(CatchClauses, Line, Bump),
+    FixedAfter = fix_expr(After, Line, Bump),
+    {'try',A,FixedExprs,FixedClauses,FixedCatchClauses,FixedAfter};
+fix_expr([E | Es], Line, Bump) ->
+    [fix_expr(E, Line, Bump) | fix_expr(Es, Line, Bump)];
+fix_expr(T, Line, Bump) when is_tuple(T) ->
+    list_to_tuple(fix_expr(tuple_to_list(T), Line, Bump));
+fix_expr(E, _Line, _Bump) ->
+    E.
+
+fix_clauses([], _Line, _Bump) ->
+    [];
+fix_clauses(Cs, Line, Bump) ->
+    case bumps_line(lists:last(Cs), Line) of
+        true ->
+            fix_cls(Cs, Line, Bump);
+        false ->
+            Cs
+    end.
+
+fix_cls([], _Line, _Bump) ->
+    [];
+fix_cls([Cl | Cls], Line, Bump) ->
+    case bumps_line(Cl, Line) of
+        true ->
+            [fix_expr(C, Line, Bump) || C <- [Cl | Cls]];
+        false ->
+            {clause,CA,P,G,Body} = Cl,
+            UniqueVarName = list_to_atom(lists:concat(["$cover$ ",Line])),
+            A = erl_anno:new(0),
+            V = {var,A,UniqueVarName},
+            [Last|Rest] = reverse(Body),
+            Body1 = reverse(Rest, [{match,A,V,Last},Bump,V]),
+            [{clause,CA,P,G,Body1} | fix_cls(Cls, Line, Bump)]
+    end.
+
+bumps_line(E, L) ->
+    try bumps_line1(E, L) catch true -> true end.
+
+bumps_line1({?EXECUTABLE_LINE,Line,_}, Line) ->
+    throw(true);
+bumps_line1([E | Es], Line) ->
+    bumps_line1(E, Line),
+    bumps_line1(Es, Line);
+bumps_line1(T, Line) when is_tuple(T) ->
+    bumps_line1(tuple_to_list(T), Line);
+bumps_line1(_, _) ->
+    false.
+
+%% Insert an executable_line instruction in the abstract code.
+bump_call(Vars, Line) ->
+    #vars{module=M,function=F,arity=A,clause=C,index_fun=GetIndex} = Vars,
+    Index = GetIndex(M, F, A, C, Line),
+    {?EXECUTABLE_LINE,Line,Index}.
+
+%%% End of fix of last expression.
+
+munge_expr({match,Anno,ExprL,ExprR}, Vars0) ->
+    {MungedExprL, Vars1} = munge_expr(ExprL, Vars0),
+    {MungedExprR, Vars2} = munge_expr(ExprR, Vars1),
+    {{match,Anno,MungedExprL,MungedExprR}, Vars2};
+munge_expr({maybe_match,Anno,ExprL,ExprR}, Vars0) ->
+    {MungedExprL, Vars1} = munge_expr(ExprL, Vars0),
+    {MungedExprR, Vars2} = munge_expr(ExprR, Vars1),
+    {{maybe_match,Anno,MungedExprL,MungedExprR}, Vars2};
+munge_expr({tuple,Anno,Exprs}, Vars0) ->
+    {MungedExprs, Vars1} = munge_exprs(Exprs, Vars0),
+    {{tuple,Anno,MungedExprs}, Vars1};
+munge_expr({record,Anno,Name,Exprs}, Vars0) ->
+    {MungedExprFields, Vars1} = munge_exprs(Exprs, Vars0),
+    {{record,Anno,Name,MungedExprFields}, Vars1};
+munge_expr({record,Anno,Arg,Name,Exprs}, Vars0) ->
+    {MungedArg, Vars1} = munge_expr(Arg, Vars0),
+    {MungedExprFields, Vars2} = munge_exprs(Exprs, Vars1),
+    {{record,Anno,MungedArg,Name,MungedExprFields}, Vars2};
+munge_expr({record_field,Anno,ExprL,ExprR}, Vars0) ->
+    {MungedExprR, Vars1} = munge_expr(ExprR, Vars0),
+    {{record_field,Anno,ExprL,MungedExprR}, Vars1};
+munge_expr({map,Anno,Fields}, Vars0) ->
+    {MungedFields, Vars1} = munge_exprs(Fields, Vars0),
+    {{map,Anno,MungedFields}, Vars1};
+munge_expr({map,Anno,Arg,Fields}, Vars0) ->
+    {MungedArg, Vars1} = munge_expr(Arg, Vars0),
+    {MungedFields, Vars2} = munge_exprs(Fields, Vars1),
+    {{map,Anno,MungedArg,MungedFields}, Vars2};
+munge_expr({map_field_assoc,Anno,Name,Value}, Vars0) ->
+    {MungedName, Vars1} = munge_expr(Name, Vars0),
+    {MungedValue, Vars2} = munge_expr(Value, Vars1),
+    {{map_field_assoc,Anno,MungedName,MungedValue}, Vars2};
+munge_expr({map_field_exact,Anno,Name,Value}, Vars0) ->
+    {MungedName, Vars1} = munge_expr(Name, Vars0),
+    {MungedValue, Vars2} = munge_expr(Value, Vars1),
+    {{map_field_exact,Anno,MungedName,MungedValue}, Vars2};
+munge_expr({cons,Anno,ExprH,ExprT}, Vars0) ->
+    {MungedExprH, Vars1} = munge_expr(ExprH, Vars0),
+    {MungedExprT, Vars2} = munge_expr(ExprT, Vars1),
+    {{cons,Anno,MungedExprH,MungedExprT}, Vars2};
+munge_expr({op,Anno,Op,ExprL,ExprR}, Vars0) ->
+    {MungedExprL, Vars1} = munge_expr(ExprL, Vars0),
+    {MungedExprR, Vars2} = munge_expr(ExprR, Vars1),
+    {{op,Anno,Op,MungedExprL,MungedExprR}, Vars2};
+munge_expr({op,Anno,Op,Expr}, Vars0) ->
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    {{op,Anno,Op,MungedExpr}, Vars1};
+munge_expr({'catch',Anno,Expr}, Vars0) ->
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    {{'catch',Anno,MungedExpr}, Vars1};
+munge_expr({call,Anno1,{remote,Anno2,ExprM,ExprF},Exprs}, Vars0) ->
+    {MungedExprM, Vars1} = munge_expr(ExprM, Vars0),
+    {MungedExprF, Vars2} = munge_expr(ExprF, Vars1),
+    {MungedExprs, Vars3} = munge_exprs(Exprs, Vars2),
+    {{call,Anno1,{remote,Anno2,MungedExprM,MungedExprF},MungedExprs}, Vars3};
+munge_expr({call,Anno,Expr,Exprs}, Vars0) ->
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    {MungedExprs, Vars2} = munge_exprs(Exprs, Vars1),
+    {{call,Anno,MungedExpr,MungedExprs}, Vars2};
+munge_expr({lc,Anno,Expr,Qs}, Vars0) ->
+    {MungedExpr, Vars1} = munge_expr(?BLOCK1(Expr), Vars0),
+    {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1),
+    {{lc,Anno,MungedExpr,MungedQs}, Vars2};
+munge_expr({bc,Anno,Expr,Qs}, Vars0) ->
+    {MungedExpr,Vars1} = munge_expr(?BLOCK1(Expr), Vars0),
+    {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1),
+    {{bc,Anno,MungedExpr,MungedQs}, Vars2};
+munge_expr({mc,Anno,{map_field_assoc,FAnno,K,V},Qs}, Vars0) ->
+    Expr = {map_field_assoc,FAnno,?BLOCK1(K),?BLOCK1(V)},
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1),
+    {{mc,Anno,MungedExpr,MungedQs}, Vars2};
+munge_expr({block,Anno,Body}, Vars0) ->
+    {MungedBody, Vars1} = munge_body(Body, Vars0),
+    {{block,Anno,MungedBody}, Vars1};
+munge_expr({'if',Anno,Clauses}, Vars0) ->
+    {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0),
+    {{'if',Anno,MungedClauses}, Vars1};
+munge_expr({'case',Anno,Expr,Clauses}, Vars0) ->
+    {MungedExpr,Vars1} = munge_expr(Expr, Vars0),
+    {MungedClauses,Vars2} = munge_clauses(Clauses, Vars1),
+    {{'case',Anno,MungedExpr,MungedClauses}, Vars2};
+munge_expr({'receive',Anno,Clauses}, Vars0) ->
+    {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0),
+    {{'receive',Anno,MungedClauses}, Vars1};
+munge_expr({'receive',Anno,Clauses,Expr,Body}, Vars0) ->
+    {MungedExpr,Vars1} = munge_expr(Expr, Vars0),
+    {MungedClauses,Vars2} = munge_clauses(Clauses, Vars1),
+    {MungedBody,Vars3} = munge_body(Body, Vars2#vars{lines=Vars1#vars.lines}),
+    Vars4 = Vars3#vars{lines=Vars2#vars.lines ++ new_bumps(Vars3, Vars2)},
+    {{'receive',Anno,MungedClauses,MungedExpr,MungedBody}, Vars4};
+munge_expr({'try',Anno,Body,Clauses,CatchClauses,After}, Vars0) ->
+    {MungedBody, Vars01} = munge_body(Body, Vars0),
+    {MungedClauses, Vars1} = munge_clauses(Clauses, Vars01),
+    {MungedCatchClauses, Vars2} = munge_clauses(CatchClauses, Vars1),
+    {MungedAfter, Vars3} = munge_body(After, Vars2),
+    {{'try',Anno,MungedBody,MungedClauses,MungedCatchClauses,MungedAfter},
+     Vars3};
+munge_expr({'maybe',Anno,Exprs}, Vars0) ->
+    {MungedExprs, Vars1} = munge_body(Exprs, Vars0),
+    {{'maybe',Anno,MungedExprs}, Vars1};
+munge_expr({'maybe',MaybeAnno,Exprs,{'else',ElseAnno,Clauses}}, Vars0) ->
+    {MungedExprs, Vars1} = munge_body(Exprs, Vars0),
+    {MungedClauses, Vars2} = munge_clauses(Clauses, Vars1),
+    {{'maybe',MaybeAnno,MungedExprs,{'else',ElseAnno,MungedClauses}}, Vars2};
+munge_expr({'fun',Anno,{clauses,Clauses}}, Vars0) ->
+    {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0),
+    {{'fun',Anno,{clauses,MungedClauses}}, Vars1};
+munge_expr({named_fun,Anno,Name,Clauses}, Vars0) ->
+    {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0),
+    {{named_fun,Anno,Name,MungedClauses}, Vars1};
+munge_expr({bin,Anno,BinElements}, Vars0) ->
+    {MungedBinElements,Vars1} = munge_exprs(BinElements, Vars0),
+    {{bin,Anno,MungedBinElements}, Vars1};
+munge_expr({bin_element,Anno,Value,Size,TypeSpecifierList}, Vars0) ->
+    {MungedValue,Vars1} = munge_expr(Value, Vars0),
+    {MungedSize,Vars2} = munge_expr(Size, Vars1),
+    {{bin_element,Anno,MungedValue,MungedSize,TypeSpecifierList},Vars2};
+munge_expr(Form, Vars0) ->
+    {Form, Vars0}.
+
+munge_exprs(Exprs, Vars) ->
+    munge_exprs(Exprs, Vars, []).
+
+munge_exprs([Expr|Exprs], #vars{in_guard=true}=Vars0, MungedExprs)
+  when is_list(Expr) ->
+    {MungedExpr, _Vars0} = munge_exprs(Expr, Vars0),
+    munge_exprs(Exprs, Vars0, [MungedExpr|MungedExprs]);
+munge_exprs([Expr|Exprs], Vars0, MungedExprs) ->
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    munge_exprs(Exprs, Vars1, [MungedExpr|MungedExprs]);
+munge_exprs([], Vars0, MungedExprs) ->
+    {reverse(MungedExprs), Vars0}.
+
+%% Every qualifier is decorated with a counter.
+munge_qualifiers(Qualifiers, Vars) ->
+    munge_qs(Qualifiers, Vars, []).
+
+munge_qs([{generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) ->
+    A = element(2, Expr),
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    munge_qs1(Qs, A, {generate,Anno,Pattern,MungedExpr}, Vars0, Vars1, MQs);
+munge_qs([{b_generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) ->
+    A = element(2, Expr),
+    {MExpr, Vars1} = munge_expr(Expr, Vars0),
+    munge_qs1(Qs, A, {b_generate,Anno,Pattern,MExpr}, Vars0, Vars1, MQs);
+munge_qs([{m_generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) ->
+    A = element(2, Expr),
+    {MExpr, Vars1} = munge_expr(Expr, Vars0),
+    munge_qs1(Qs, A, {m_generate,Anno,Pattern,MExpr}, Vars0, Vars1, MQs);
+munge_qs([Expr|Qs], Vars0, MQs) ->
+    A = element(2, Expr),
+    {MungedExpr, Vars1} = munge_expr(Expr, Vars0),
+    munge_qs1(Qs, A, MungedExpr, Vars0, Vars1, MQs);
+munge_qs([], Vars0, MQs) ->
+    {reverse(MQs), Vars0}.
+
+munge_qs1(Qs, Anno, NQ, Vars0, Vars1, MQs) ->
+    case new_bumps(Vars1, Vars0) of
+        [_] ->
+            munge_qs(Qs, Vars1, [NQ | MQs]);
+        _ ->
+            {MungedTrue, Vars2} = munge_expr(?BLOCK({atom,Anno,true}), Vars1),
+            munge_qs(Qs, Vars2, [NQ, MungedTrue | MQs])
+    end.
+
+new_bumps(#vars{lines=New}, #vars{lines=Old}) ->
+    subtract(New, Old).
+
+subtract(L1, L2) ->
+    [E || E <- L1, not member(E, L2)].
+
+common_elems(L1, L2) ->
+    [E || E <- L1, member(E, L2)].
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 7a3c8738b2..2f5db48076 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -976,6 +976,10 @@ expr({op,L,Op,L0,R0}, St0) ->
     {#icall{anno=#a{anno=LineAnno},		%Must have an #a{}
 	    module=#c_literal{anno=LineAnno,val=erlang},
 	    name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1};
+expr({executable_line,L,_}, St0) ->
+    {#iprimop{anno=#a{anno=lineno_anno(L, St0)},
+              name=#c_literal{val=executable_line},
+              args=[]},[],St0};
 expr({ssa_check_when,L,WantedResult,Args,Tag,Clauses}, St) ->
     {#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,Clauses}}, [], St}.
 
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 7efafc5e35..43c7ccec7a 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -122,6 +122,8 @@ R25= \
 	bs_utf \
 	bs_bincomp
 
+COVER=$(NO_OPT)
+
 DIALYZER = bs_match
 
 CORE_MODULES = \
@@ -156,6 +158,8 @@ NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE)
 NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl)
 DIALYZER_MODULES= $(DIALYZER:%=%_dialyzer_SUITE)
 DIALYZER_ERL_FILES= $(DIALYZER_MODULES:%=%.erl)
+COVER_MODULES= $(COVER:%=%_cover_SUITE)
+COVER_ERL_FILES= $(COVER_MODULES:%=%.erl)
 
 ERL_FILES= $(MODULES:%=%.erl)
 CORE_FILES= $(CORE_MODULES:%=%.core)
@@ -190,7 +194,7 @@ DISABLE_SSA_OPT = +no_bool_opt +no_share_opt +no_bsm_opt +no_fun_opt +no_ssa_opt
   $(NO_CORE_OPT_ERL_FILES) $(NO_CORE_SSA_OPT_ERL_FILES) \
   $(INLINE_ERL_FILES) $(R23_ERL_FILES) \
   $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) \
-  $(DIALYZER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES)
+  $(DIALYZER_ERL_FILES) $(COVER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES)
 	$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
 	  > $(EMAKEFILE)
 	$(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) +no_postopt \
@@ -215,6 +219,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES
 	  -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
 	$(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \
 	  -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE)
+	$(ERL_TOP)/make/make_emakefile +line_coverage $(ERL_COMPILE_FLAGS) \
+	  -o$(EBIN) $(COVER_MODULES) >> $(EMAKEFILE)
 	$(ERL_TOP)/make/make_emakefile +dialyzer $(ERL_COMPILE_FLAGS) \
 	  -o$(EBIN) $(DIALYZER_MODULES) >> $(EMAKEFILE)
 
@@ -265,6 +271,9 @@ docs:
 %_no_type_opt_SUITE.erl: %_SUITE.erl
 	sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
 
+%_cover_SUITE.erl: %_SUITE.erl
+	sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
 %_dialyzer_SUITE.erl: %_SUITE.erl
 	sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
 
@@ -288,6 +297,7 @@ release_tests_spec: make_emakefile
 		$(NO_MOD_OPT_ERL_FILES) \
 		$(NO_SSA_OPT_ERL_FILES) \
 	        $(NO_TYPE_OPT_ERL_FILES) \
+	        $(COVER_ERL_FILES) \
 	        $(DIALYZER_ERL_FILES) "$(RELSYSDIR)"
 	$(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)"
 	for file in $(ERL_DUMMY_FILES); do \
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index 41877a9e84..897501338c 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -688,6 +688,8 @@ cs_end() ->
 %% Verify that the allocated size is exact (rounded up to the nearest byte).
 cs(Bin) ->
     case ?MODULE of
+        bs_bincomp_cover_SUITE ->
+            ok;
         bs_bincomp_no_opt_SUITE ->
             ok;
         bs_bincomp_no_ssa_opt_SUITE ->
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index de610062dc..205ec00a96 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -90,6 +90,7 @@ opt_opts(Mod) ->
                      ({feature,_,enable}) -> true;
                      ({feature,_,disable}) -> true;
                      (inline) -> true;
+                     (line_coverage) -> true;
                      (no_bs_create_bin) -> true;
                      (no_bsm_opt) -> true;
                      (no_bs_match) -> true;
@@ -123,7 +124,8 @@ get_data_dir(Config) ->
                 "_inline_SUITE",
                 "_no_module_opt_SUITE",
                 "_no_type_opt_SUITE",
-                "_no_ssa_opt_SUITE"],
+                "_no_ssa_opt_SUITE",
+                "_cover_SUITE"],
     lists:foldl(fun(Suffix, Acc) ->
                         Opts = [{return,list}],
                         re:replace(Acc, Suffix, "_SUITE", Opts)
@@ -144,6 +146,7 @@ is_cloned_mod_1("_no_type_opt_SUITE") -> true;
 is_cloned_mod_1("_post_opt_SUITE") -> true;
 is_cloned_mod_1("_inline_SUITE") -> true;
 is_cloned_mod_1("_no_module_opt_SUITE") -> true;
+is_cloned_mod_1("_cover_SUITE") -> true;
 is_cloned_mod_1([_|T]) -> is_cloned_mod_1(T);
 is_cloned_mod_1([]) -> false.
 
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 706b5fec6e..c0d2d1bcd3 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -452,7 +452,9 @@ expr({op,Anno,Op,L0,R0}, St0) ->
     {L,St1} = expr(L0, St0),
     {R,St2} = expr(R0, St1),
     {{op,Anno,Op,L,R},St2};
-expr(E={ssa_check_when,_,_,_,_,_}, St) ->
+expr({executable_line,_,_}=E, St) ->
+    {E, St};
+expr({ssa_check_when,_,_,_,_,_}=E, St) ->
     {E, St}.
 
 expr_list([E0 | Es0], St0) ->
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 107b0970cf..766aade3a0 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2587,6 +2587,8 @@ expr({op,_Anno,_Op,L,R}, Vt, St) ->
 %% The following are not allowed to occur anywhere!
 expr({remote,_Anno,M,_F}, _Vt, St) ->
     {[],add_error(erl_parse:first_anno(M), illegal_expr, St)};
+expr({executable_line,_,_}, _Vt, St) ->
+    {[], St};
 expr({ssa_check_when,_Anno,_WantedResult,_Args,_Tag,_Exprs}, _Vt, St) ->
     {[], St}.
 
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 50ff87643c..a2399886ed 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -755,6 +755,8 @@ lexpr({remote,_,M,F}, Prec, Opts) ->
 %% BIT SYNTAX:
 lexpr({bin,_,Fs}, _, Opts) ->
     bit_grp(Fs, Opts);
+lexpr({executable_line,Line,Index}, _Prec, _Opts) ->
+    leaf(format("beam_instruction:executable_line(~p, ~p)", [Line,Index]));
 %% Special case for straight values.
 lexpr({value,_,Val}, _,_) ->
     {value,Val};
-- 
2.35.3

openSUSE Build Service is sponsored by