File 2744-v3_core-Handle-binary-generators-with-repeated-varia.patch of Package erlang
From 40c8bd7f14e512953b153565edf5d8daea674aea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Sat, 21 Nov 2020 08:32:00 +0100
Subject: [PATCH 4/5] v3_core: Handle binary generators with repeated variables
v3_core would generate incorrect code for binary generators
such as:
foo() ->
[V || <<V,V>> <= Gen].
The generated code (translated back to Erlang) would look similar to
this:
foo(Gen) ->
foo_lc(Gen).
foo_lc(<<V,V,Tail/binary>>) ->
[V|foo_lc(Tail)];
foo_lc(_) ->
[].
That is, the generated code lacks a clause that will continue
the iteration if the first clause fails to match.
Correct the code generation to ensure that a skip clause is generated:
foo_lc(<<V,V,Tail/binary>>) ->
[V|foo_lc(Tail)];
foo_lc(<<V,NewVar,Tail/binary>>) ->
foo_lc(Tail);
foo_lc(_) ->
[].
---
lib/compiler/src/v3_core.erl | 103 +++++++++++++------------
lib/compiler/test/bs_bincomp_SUITE.erl | 52 ++++++++++---
2 files changed, 94 insertions(+), 61 deletions(-)
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index a14ad988ae..7b14e15b8c 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -1389,27 +1389,19 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
Nc = #iapply{anno=GAnno,op=F,args=[Tail]},
{[FcVar,Var],St2} = new_vars(2, St1),
Fc = bad_generator([FcVar], FcVar, Arg),
+ SkipClause = #iclause{anno=#a{anno=[skip_clause,compiler_generated|LA]},
+ pats=[SkipPat],guard=[],body=[Nc]},
TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]},
- Cs0 = case {AccPat,AccGuard} of
- {SkipPat,[]} ->
- %% Skip and accumulator patterns are the same and there is
- %% no guard, no need to generate a skip clause.
- [TailClause];
- _ ->
- [#iclause{anno=#a{anno=[compiler_generated|LA]},
- pats=[SkipPat],guard=[],body=[Nc]},
- TailClause]
- end,
- {Cs,St4} = case AccPat of
- nomatch ->
- %% The accumulator pattern never matches, no need
- %% for an accumulator clause.
- {Cs0,St2};
+ {Cs,St4} = case {AccPat,SkipPat} of
+ {nomatch,nomatch} ->
+ {[TailClause],St2};
+ {nomatch,_} ->
+ {[SkipClause,TailClause],St2};
_ ->
{Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2),
- {[#iclause{anno=LAnno,pats=[AccPat],guard=AccGuard,
- body=Lps ++ [Lc]}|Cs0],
- St3}
+ AccClause = #iclause{anno=LAnno,pats=[AccPat],guard=AccGuard,
+ body=Lps ++ [Lc]},
+ {[AccClause,SkipClause,TailClause],St3}
end,
Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
{#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
@@ -1459,30 +1451,21 @@ bc_tq1(Line, E, [#igen{anno=GAnno,
F = #c_var{anno=LA,name={Name,2}},
Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]},
Fc = bad_generator(FcVars, hd(FcVars), Arg),
+ SkipClause = #iclause{anno=#a{anno=[compiler_generated,skip_clause|LA]},
+ pats=[SkipPat,IgnoreVar],guard=[],body=[Nc]},
TailClause = #iclause{anno=LAnno,pats=[TailPat,IgnoreVar],guard=[],
body=[AccVar]},
- Cs0 = case {AccPat,AccGuard} of
- {SkipPat,[]} ->
- %% Skip and accumulator patterns are the same and there is
- %% no guard, no need to generate a skip clause.
- [TailClause];
- _ ->
- [#iclause{anno=#a{anno=[compiler_generated|LA]},
- pats=[SkipPat,IgnoreVar],guard=[],body=[Nc]},
- TailClause]
- end,
- {Cs,St} = case AccPat of
- nomatch ->
- %% The accumulator pattern never matches, no need
- %% for an accumulator clause.
- {Cs0,St4};
- _ ->
+ {Cs,St} = case {AccPat,SkipPat} of
+ {nomatch,nomatch} ->
+ {[TailClause],St4};
+ {nomatch,_} ->
+ {[SkipClause,TailClause],St4};
+ {_,_} ->
{Bc,Bps,St5} = bc_tq1(Line, E, Qs, AccVar, St4),
Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc],
- {[#iclause{anno=LAnno,
- pats=[AccPat,IgnoreVar],guard=AccGuard,
- body=Body}|Cs0],
- St5}
+ AccClause = #iclause{anno=LAnno,pats=[AccPat,IgnoreVar],
+ guard=AccGuard,body=Body},
+ {[AccClause,SkipClause,TailClause],St5}
end,
Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc},
@@ -1668,7 +1651,7 @@ generator(Line, {b_generate,Lg,P,E}, Gs, St0) ->
{AccSegs,Tail,TailSeg,St2} = append_tail_segment(Segs, St1),
AccPat = Cp#ibinary{segments=AccSegs},
{Cg,St3} = lc_guard_tests(Gs, St2),
- {SkipSegs,St4} = emasculate_segments(AccSegs, St3),
+ {SkipSegs,St4} = skip_segments(AccSegs, St3, []),
SkipPat = Cp#ibinary{segments=SkipSegs},
{Ce,Pre,St5} = safe(E, St4),
Gen = #igen{anno=#a{anno=GA},acc_pat=AccPat,acc_guard=Cg,
@@ -1694,15 +1677,21 @@ append_tail_segment(Segs, St0) ->
flags=#c_literal{val=[unsigned,big]}},
{Segs++[Tail],Var,Tail,St}.
-emasculate_segments(Segs, St) ->
- emasculate_segments(Segs, St, []).
-
-emasculate_segments([#ibitstr{val=#c_var{}}=B|Rest], St, Acc) ->
- emasculate_segments(Rest, St, [B|Acc]);
-emasculate_segments([B|Rest], St0, Acc) ->
+%% skip_segments(Segments, St0, Acc) -> {SkipSegments,St}.
+%% Generate the segments for a binary pattern that can be used
+%% in the skip clause that will continue the iteration when
+%% the accumulator pattern didn't match.
+
+skip_segments([#ibitstr{val=#c_var{}}=B|Rest], St, Acc) ->
+ %% We must keep the names of existing variables to ensure that
+ %% patterns such as <<Size,X:Size>> will work.
+ skip_segments(Rest, St, [B|Acc]);
+skip_segments([B|Rest], St0, Acc) ->
+ %% Replace literal or expression with a variable (whose value will
+ %% be ignored).
{Var,St1} = new_var(St0),
- emasculate_segments(Rest, St1, [B#ibitstr{val=Var}|Acc]);
-emasculate_segments([], St, Acc) ->
+ skip_segments(Rest, St1, [B#ibitstr{val=Var}|Acc]);
+skip_segments([], St, Acc) ->
{reverse(Acc),St}.
lc_guard_tests([], St) -> {[],St};
@@ -2145,8 +2134,22 @@ uclause(Cl0, Ks, St0) ->
A = A0#a{us=Used,ns=New},
{Cl1#iclause{anno=A},St1}.
-do_uclause(#iclause{anno=Anno,pats=Ps0,guard=G0,body=B0}, Ks0, St0) ->
- {Ps1,Pg,Pvs,Pus,St1} = upattern_list(Ps0, Ks0, St0),
+do_uclause(#iclause{anno=A0,pats=Ps0,guard=G0,body=B0}, Ks0, St0) ->
+ {Ps1,Pg0,Pvs,Pus,St1} = upattern_list(Ps0, Ks0, St0),
+ Anno = A0#a.anno,
+ {Pg,A} = case member(skip_clause, Anno) of
+ true ->
+ %% This is the skip clause for a binary generator.
+ %% To ensure that it will properly skip the nonmatching
+ %% patterns in generators such as:
+ %%
+ %% <<V,V>> <= Gen
+ %%
+ %% we must remove any generated pre guard.
+ {[],A0#a{anno=Anno -- [skip_clause]}};
+ false ->
+ {Pg0,A0}
+ end,
Pu = union(Pus, intersection(Pvs, Ks0)),
Pn = subtract(Pvs, Pu),
Ks1 = union(Pn, Ks0),
@@ -2157,7 +2160,7 @@ do_uclause(#iclause{anno=Anno,pats=Ps0,guard=G0,body=B0}, Ks0, St0) ->
{B1,St3} = uexprs(B0, Ks2, St2),
Used = intersection(union([Pu,Gu,used_in_any(B1)]), Ks0),
New = union([Pn,Gn,new_in_any(B1)]),
- {#iclause{anno=Anno,pats=Ps1,guard=G1,body=B1},Pvs,Used,New,St3}.
+ {#iclause{anno=A,pats=Ps1,guard=G1,body=B1},Pvs,Used,New,St3}.
%% uguard([Test], [Kexpr], [KnownVar], State) -> {[Kexpr],State}.
%% Build a guard expression list by folding in the equality tests.
diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl
index b9d3d3eb4a..6386bce752 100644
--- a/lib/compiler/test/bs_bincomp_SUITE.erl
+++ b/lib/compiler/test/bs_bincomp_SUITE.erl
@@ -26,8 +26,8 @@
init_per_group/2,end_per_group/2,
byte_aligned/1,bit_aligned/1,extended_byte_aligned/1,
extended_bit_aligned/1,mixed/1,filters/1,trim_coverage/1,
- nomatch/1,sizes/1,general_expressions/1,matched_out_size/1,
- no_generator/1,zero_pattern/1]).
+ nomatch/1,sizes/1,general_expressions/1,
+ no_generator/1,zero_pattern/1,multiple_segments/1]).
-include_lib("common_test/include/ct.hrl").
@@ -36,8 +36,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[byte_aligned, bit_aligned, extended_byte_aligned,
extended_bit_aligned, mixed, filters, trim_coverage,
- nomatch, sizes, general_expressions, matched_out_size,
- no_generator, zero_pattern].
+ nomatch, sizes, general_expressions,
+ no_generator, zero_pattern, multiple_segments].
groups() ->
[].
@@ -446,13 +446,6 @@ enc_char_cm(C0, Lb, Limit) ->
-undef(BAD).
-matched_out_size(Config) when is_list(Config) ->
- <<1, 2>> = matched_out_size_1(<<4, 1:4, 4, 2:4>>),
- ok.
-
-matched_out_size_1(Binary) ->
- << <<X>> || <<S, X:S>> <= Binary>>.
-
no_generator(_Config) ->
[<<"abc">>] = [<<(id(<<"abc">>)) || true >>],
{<<>>} = {<<(id(<<"abc">>)) || false >>},
@@ -483,6 +476,43 @@ zero_pattern(Config) ->
ok
end.
+multiple_segments(_Config) ->
+ cs_init(),
+
+ [1,2] = matched_out_size(<<4, 1:4, 4, 2:4>>),
+ [42] = matched_out_size(<<16, 42:16, 72>>),
+
+ [] = do_multiple_segments_1(<<>>),
+ [] = do_multiple_segments_1(<<1>>),
+ [] = do_multiple_segments_1(<<1,2>>),
+ [] = do_multiple_segments_1(<<1,2,3>>),
+ [1,4] = do_multiple_segments_1(<<99,0,1,1,2,3,4,4>>),
+
+ [] = do_multiple_segments_2(<<1,2>>),
+ [6] = do_multiple_segments_2(<<1,2,3>>),
+ [6,15] = do_multiple_segments_2(<<1,2,3,4,5,6,7,8>>),
+
+ cs_end(),
+ ok.
+
+matched_out_size(Gen) ->
+ Bin = cs_default(<< <<X>> || <<S,X:S>> <= Gen >>),
+ List = [X || <<S,X:S>> <= Gen],
+ Bin = list_to_binary(List),
+ List.
+
+do_multiple_segments_1(Gen) ->
+ Bin = cs_default(<< <<V>> || <<V,V>> <= Gen >>),
+ List = [V || <<V,V>> <= Gen],
+ Bin = list_to_binary(List),
+ List.
+
+do_multiple_segments_2(Gen) ->
+ Bin = cs(<< <<(A+B+C)>> || <<A,B,C>> <= Gen >>),
+ List = [A+B+C || <<A,B,C>> <= Gen],
+ Bin = list_to_binary(List),
+ List.
+
cs_init() ->
erts_debug:set_internal_state(available_internal_state, true),
ok.
--
2.26.2