File 8921-Optimize-binary-matching-code.patch of Package erlang
From e672410b63f0865f3b8ecbd664f91db5f0812e70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Fri, 22 Aug 2025 08:23:15 +0200
Subject: [PATCH] Optimize binary matching code
The BEAM code resulting from complex binary match code sometimes
repeats a test that has failed. This commit eliminates some of those
repeated tests. That can result in a considerable code size reduction
for functions doing complex binary matching. Here are some example of
modules where that happened: `dets_v9`, `ssl_gen_statem`,
`ssl_handshake`, and `zip`.
This optimization also avoids repeating a failed call to a guard BIF.
---
lib/compiler/src/beam_ssa_dead.erl | 80 +++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index 9abb001c48..1bc16a192c 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -62,7 +62,8 @@ opt(Linear0) ->
Blocks0 = maps:from_list(Linear0),
St0 = #st{bs=Blocks0,us=Used,skippable=Skippable},
St = shortcut_opt(St0),
- #st{bs=Blocks} = combine_eqs(St#st{us=#{}}),
+ #st{bs=Blocks1} = combine_eqs(St#st{us=#{}}),
+ Blocks = shortcut_failed_succeeded(Blocks1),
opt_redundant_tests(Blocks).
%%%
@@ -860,6 +861,83 @@ eval_type_test_1({is_tagged_tuple,Sz,Tag}, Arg) ->
eval_type_test_1(Test, Arg) ->
erlang:Test(Arg).
+%%%
+%%% Shortcut repeated failed `succeeded:guard` tests. Consider this
+%%% code:
+%%%
+%%% 0:
+%%% V1 = bs_start_match `new`, V0
+%%% B1 = succeeded:guard V1
+%%% br B1, ^Succ1, ^29
+%%% .
+%%% .
+%%% .
+%%% 29:
+%%% V2 = bs_start_match `new`, V0
+%%% B2 = succeeded:guard _V2
+%%% br B2, ^Succ2, ^36
+%%%
+%%% If the `bs_start_match` instruction in block 0 fails, the
+%%% `bs_start_match` instruction in block 29 will also fail.
+%%% Therefore, the `br` instruction can be modified to have use
+%%% the same failure label as in block 29:
+%%%
+%%% 0:
+%%% V1 = bs_start_match `new`, V0
+%%% B1 = succeeded:guard V1
+%%% br B1, ^Succ1, ^36
+%%% .
+%%% .
+%%% .
+%%% 29:
+%%% V2 = bs_start_match `new`, V0
+%%% B2 = succeeded:guard _V2
+%%% br B2, ^Succ2, ^36
+%%%
+%%% If the only reference to block 29 was from block 0, block 29 will
+%%% be removed, and if block Succ2 was only referenced from block 29
+%%% block Succ2 would also be removed, and so on. For some functions
+%%% with complex binary matching, that could result in many blocks
+%%% being removed. One example of such a function is
+%%% `ssl_gen_statem:read_application_dist_data/5`.
+%%%
+%%% The same idea works for any instruction that precedes
+%%% `succeeded:guard`.
+%%%
+
+shortcut_failed_succeeded(Blocks) ->
+ Ls = reverse(beam_ssa:rpo(Blocks)),
+ shortcut_failed_succeeded(Ls, #{}, Blocks).
+
+shortcut_failed_succeeded([L|Ls], FailMap0, Blocks0) ->
+ Blk0 = map_get(L, Blocks0),
+ case Blk0 of
+ #b_blk{is=[#b_set{op=Op,dst=Result,args=Args},
+ #b_set{op={succeeded,guard},dst=Bool,args=[Result]}],
+ last=#b_br{bool=Bool,fail=Fail0}=Br}
+ when Op =/= get_map_element ->
+ %% We don't do this optimization for `get_map_element`
+ %% because that would prevent multiple `get_map_element`
+ %% instructions to be combined into a single
+ %% `get_map_elements` BEAM instruction.
+ Instr = {Op,Args},
+ case FailMap0 of
+ #{Fail0 := {Instr,Fail}} ->
+ %% The code at label Fail0 will always transfer
+ %% control to label Fail.
+ Blk = Blk0#b_blk{last=Br#b_br{fail=Fail}},
+ Blocks = Blocks0#{L := Blk},
+ FailMap = FailMap0#{L => {Instr,Fail}},
+ shortcut_failed_succeeded(Ls, FailMap, Blocks);
+ #{} ->
+ FailMap = FailMap0#{L => {Instr,Fail0}},
+ shortcut_failed_succeeded(Ls, FailMap, Blocks0)
+ end;
+ #b_blk{} ->
+ shortcut_failed_succeeded(Ls, FailMap0, Blocks0)
+ end;
+shortcut_failed_succeeded([], _FailMap, Blocks) -> Blocks.
+
%%%
%%% Combine bif:'=:=', is_boolean/1 tests, and switch instructions
%%% to switch instructions.
--
2.51.0