1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2018-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(beam_ssa_SUITE). 21 22-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, 23 init_per_group/2,end_per_group/2, 24 calls/1,tuple_matching/1,recv/1,maps/1, 25 cover_ssa_dead/1,combine_sw/1,share_opt/1, 26 beam_ssa_dead_crash/1,stack_init/1, 27 mapfoldl/0,mapfoldl/1, 28 grab_bag/1,coverage/1]). 29 30suite() -> [{ct_hooks,[ts_install_cth]}]. 31 32all() -> 33 [mapfoldl, 34 {group,p}]. 35 36groups() -> 37 [{p,test_lib:parallel(), 38 [tuple_matching, 39 calls, 40 recv, 41 maps, 42 cover_ssa_dead, 43 combine_sw, 44 share_opt, 45 beam_ssa_dead_crash, 46 stack_init, 47 grab_bag, 48 coverage 49 ]}]. 50 51init_per_suite(Config) -> 52 test_lib:recompile(?MODULE), 53 Config. 54 55end_per_suite(_Config) -> 56 ok. 57 58init_per_group(_GroupName, Config) -> 59 Config. 60 61end_per_group(_GroupName, Config) -> 62 Config. 63 64calls(Config) -> 65 Ret = {return,value,Config}, 66 Ret = fun_call(fun(42) -> ok end, Ret), 67 Ret = apply_fun(fun(a, b) -> ok end, [a,b], Ret), 68 Ret = apply_mfa(test_lib, id, [anything], Ret), 69 {'EXIT',{badarg,_}} = (catch call_error()), 70 {'EXIT',{badarg,_}} = (catch call_error(42)), 71 5 = start_it([erlang,length,1,2,3,4,5]), 72 ok. 73 74fun_call(Fun, X0) -> 75 X = id(X0), 76 Fun(42), 77 X. 78 79apply_fun(Fun, Args, X0) -> 80 X = id(X0), 81 apply(Fun, Args), 82 X. 83 84apply_mfa(Mod, Name, Args, X0) -> 85 X = id(X0), 86 apply(Mod, Name, Args), 87 X. 88 89call_error() -> 90 error(badarg), 91 ok. 92 93call_error(I) -> 94 <<I:(-8)>>, 95 ok. 96 97start_it([_|_]=MFA) -> 98 case MFA of 99 [M,F|Args] -> M:F(Args) 100 end. 101 102tuple_matching(_Config) -> 103 do_tuple_matching({tag,42}), 104 105 true = is_two_tuple({a,b}), 106 false = is_two_tuple({a,b,c}), 107 false = is_two_tuple(atom), 108 109 ok. 110 111do_tuple_matching(Arg) -> 112 Res = do_tuple_matching_1(Arg), 113 Res = do_tuple_matching_2(Arg), 114 Res = do_tuple_matching_3(Arg), 115 Res. 116 117do_tuple_matching_1({tag,V}) -> 118 {ok,V}. 119 120do_tuple_matching_2(Tuple) when is_tuple(Tuple) -> 121 Size = tuple_size(Tuple), 122 if 123 Size =:= 2 -> 124 {ok,element(2, Tuple)} 125 end. 126 127do_tuple_matching_3(Tuple) when is_tuple(Tuple) -> 128 Size = tuple_size(Tuple), 129 if 130 Size =:= 2 -> 131 2 = id(Size), 132 {ok,element(2, Tuple)} 133 end. 134 135is_two_tuple(Arg) -> 136 case is_tuple(Arg) of 137 false -> false; 138 true -> tuple_size(Arg) == 2 139 end. 140 141-record(reporter_state, {res,run_config}). 142-record(run_config, {report_interval=0}). 143 144recv(_Config) -> 145 Parent = self(), 146 147 %% Test sync_wait_mon/2. 148 Succ = fun() -> Parent ! {ack,self(),{result,42}} end, 149 {result,42} = sync_wait_mon(spawn_monitor(Succ), infinity), 150 151 Down = fun() -> exit(down) end, 152 {error,down} = sync_wait_mon(spawn_monitor(Down), infinity), 153 154 Exit = fun() -> 155 Self = self(), 156 spawn(fun() -> exit(Self, kill_me) end), 157 receive _ -> ok end 158 end, 159 {error,kill_me} = sync_wait_mon(spawn_monitor(Exit), infinity), 160 161 Timeout = fun() -> receive _ -> ok end end, 162 {error,timeout} = sync_wait_mon(spawn_monitor(Timeout), 0), 163 164 %% Test reporter_loop/1. 165 {a,Parent} = reporter_loop(#reporter_state{res={a,Parent}, 166 run_config=#run_config{}}), 167 168 %% Test bad_sink/0. 169 bad_sink(), 170 171 %% Test tricky_recv_1/0. 172 self() ! 1, 173 a = tricky_recv_1(), 174 self() ! 2, 175 b = tricky_recv_1(), 176 177 %% Test tricky_recv_2/0. 178 self() ! 1, 179 {1,yes} = tricky_recv_2(), 180 self() ! 2, 181 {2,maybe} = tricky_recv_2(), 182 183 %% Test 'receive after infinity' in try/catch. 184 Pid = spawn(fun recv_after_inf_in_try/0), 185 exit(Pid, done), 186 187 %% Test tricky_recv_3(). 188 self() ! {{self(),r0},{1,42,"name"}}, 189 {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_3(), 190 self() ! {{self(),r1},{2,99,<<"data">>}}, 191 {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_3(), 192 193 %% Test tricky_recv_4(). 194 self() ! {[self(),r0],{1,42,"name"}}, 195 {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_4(), 196 self() ! {[self(),r1],{2,99,<<"data">>}}, 197 {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(), 198 199 %% Test tricky_recv_5/0. 200 self() ! 1, 201 a = tricky_recv_5(), 202 self() ! 2, 203 b = tricky_recv_5(), 204 205 %% Test tricky_recv_5a/0. 206 self() ! 1, 207 a = tricky_recv_5a(), 208 self() ! 2, 209 b = tricky_recv_5a(), 210 self() ! any, 211 b = tricky_recv_5a(), 212 213 %% tricky_recv_6/0 is a compile-time error. 214 tricky_recv_6(), 215 216 recv_coverage(), 217 218 ok. 219 220sync_wait_mon({Pid, Ref}, Timeout) -> 221 receive 222 {ack,Pid,Return} -> 223 erlang:demonitor(Ref, [flush]), 224 Return; 225 {'DOWN',Ref,_Type,Pid,Reason} -> 226 {error,Reason}; 227 {'EXIT',Pid,Reason} -> 228 erlang:demonitor(Ref, [flush]), 229 {error,Reason} 230 after Timeout -> 231 erlang:demonitor(Ref, [flush]), 232 exit(Pid, kill), 233 {error,timeout} 234 end. 235 236reporter_loop(State) -> 237 RC = State#reporter_state.run_config, 238 receive after RC#run_config.report_interval -> 239 State#reporter_state.res 240 end. 241 242bad_sink() -> 243 {ok,Pid} = my_spawn(self()), 244 %% The get_tuple_element instruction for the matching 245 %% above was sinked into the receive loop. That will 246 %% not work (and would be bad for performance if it 247 %% would work). 248 receive 249 {ok,Pid} -> 250 ok; 251 error -> 252 exit(failed) 253 end, 254 exit(Pid, kill). 255 256my_spawn(Parent) -> 257 Pid = spawn(fun() -> 258 Parent ! {ok,self()}, 259 receive _ -> ok end 260 end), 261 {ok,Pid}. 262 263tricky_recv_1() -> 264 receive 265 X=1 -> 266 id(42), 267 a; 268 X=2 -> 269 b 270 end, 271 case X of 272 1 -> a; 273 2 -> b 274 end. 275 276tricky_recv_2() -> 277 receive 278 X=1 -> 279 Y = case id(X) of 280 1 -> yes; 281 _ -> no 282 end, 283 a; 284 X=2 -> 285 Y = maybe, 286 b 287 end, 288 {X,Y}. 289 290recv_after_inf_in_try() -> 291 try 292 %% Used to crash beam_kernel_to_ssa. 293 receive after infinity -> ok end 294 catch 295 _A:_B -> 296 receive after infinity -> ok end 297 end. 298 299tricky_recv_3() -> 300 {Pid, R, Request} = 301 receive 302 {{Pid0,R0}, {1, Proto0, Name0}} -> 303 {Pid0, R0, 304 [<<1:32, 1:8, Proto0:8>>,Name0,0]}; 305 {{Pid1,R1}, {2, Proto1, Data1}} -> 306 {Pid1, R1, 307 <<1:32, 2:8, Proto1:8, Data1/binary>>} 308 end, 309 id({Pid,R,Request}). 310 311tricky_recv_4() -> 312 {Pid, R, Request} = 313 receive 314 {[Pid0,R0], {1, Proto0, Name0}} -> 315 {Pid0, R0, 316 [<<1:32, 1:8, Proto0:8>>,Name0,0]}; 317 {[Pid1,R1], {2, Proto1, Data1}} -> 318 {Pid1, R1, 319 <<1:32, 2:8, Proto1:8, Data1/binary>>} 320 end, 321 id({Pid,R,Request}). 322 323%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges 324%% when fixing up receives; the call to id/2 can either succeed or land in the 325%% catch block, and we added a phi node to its immediate successor. 326tricky_recv_5() -> 327 try 328 receive 329 X=1 -> 330 id(42), 331 a; 332 X=2 -> 333 b 334 end, 335 case X of 336 1 -> a; 337 2 -> b 338 end 339 catch 340 _:_ -> c 341 end. 342 343%% beam_ssa_pre_codegen would find the wrong exit block when fixing up 344%% receives. 345tricky_recv_5a() -> 346 try 347 receive 348 X=1 -> 349 id(42), 350 a; 351 X=_ -> 352 b 353 end, 354 %% The following is the code in the common exit block. 355 if X =:= 1 -> a; 356 true -> b 357 end 358 catch 359 %% But this code with the landingpad instruction was found, 360 %% because it happened to occur before the true exit block 361 %% in the reverse post order. 362 _:_ -> c 363 end. 364 365 366%% When fixing tricky_recv_5, we introduced a compiler crash when the common 367%% exit block was ?EXCEPTION_BLOCK and floats were in the picture. 368tricky_recv_6() -> 369 RefA = make_ref(), 370 RefB = make_ref(), 371 receive 372 {RefA, Number} -> Number + 1.0; 373 {RefB, Number} -> Number + 2.0 374 after 0 -> 375 ok 376 end. 377 378recv_coverage() -> 379 self() ! 1, 380 a = recv_coverage_1(), 381 self() ! 2, 382 b = recv_coverage_1(), 383 384 self() ! 1, 385 a = recv_coverage_2(), 386 self() ! 2, 387 b = recv_coverage_2(), 388 389 ok. 390 391%% Similar to tricky_recv_5/0, but provides test coverage for the #b_switch{} 392%% terminator. 393recv_coverage_1() -> 394 receive 395 X=1 -> 396 %% Jump to common exit block through #b_switch{list=L} 397 case id(0) of 398 0 -> a; 399 1 -> b; 400 2 -> c; 401 3 -> d 402 end; 403 X=2 -> 404 %% Jump to common exit block through #b_switch{fail=F} 405 case id(42) of 406 0 -> exit(quit); 407 1 -> exit(quit); 408 2 -> exit(quit); 409 3 -> exit(quit); 410 _ -> b 411 end 412 end, 413 case X of 414 1 -> a; 415 2 -> b 416 end. 417 418%% Similar to recv_coverage_1/0, providing test coverage for #b_br{}. 419recv_coverage_2() -> 420 receive 421 X=1 -> 422 A = id(1), 423 %% Jump to common exit block through #b_br{succ=S}. 424 if 425 A =:= 1 -> a; 426 true -> exit(quit) 427 end; 428 X=2 -> 429 A = id(2), 430 %% Jump to common exit block through #b_br{fail=F}. 431 if 432 A =:= 1 -> exit(quit); 433 true -> a 434 end 435 end, 436 case X of 437 1 -> a; 438 2 -> b 439 end. 440 441maps(_Config) -> 442 {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), 443 444 {jkl,nil,nil} = maps_2(#{abc => 0, jkl => 0}), 445 {def,ghi,abc} = maps_2(#{abc => 0, def => 0}), 446 {def,ghi,jkl} = maps_2(#{def => 0, jkl => 0}), 447 {mno,nil,abc} = maps_2(#{abc => 0, mno => 0, jkl => 0}), 448 {jkl,nil,nil} = maps_2(#{jkl => 0}), 449 error = maps_2(#{}), 450 451 ok. 452 453maps_1(K) -> 454 _ = id(42), 455 #{K:=V} = #{}, 456 V. 457 458maps_2(Map) -> 459 Res = maps_2a(Map), 460 Res = maps_2b(Map), 461 Res. 462 463maps_2a(#{} = Map) -> 464 case case Abc = is_map_key(abc, Map) of 465 false -> false; 466 _ -> is_map_key(def, Map) 467 end of 468 true -> 469 {def, ghi, abc}; 470 false -> 471 case case Jkl = is_map_key(jkl, Map) of 472 false -> false; 473 _ -> is_map_key(def, Map) 474 end of 475 true -> 476 {def, ghi, jkl}; 477 false -> 478 case case Abc of 479 false -> false; 480 _ -> is_map_key(mno, Map) 481 end of 482 true -> 483 {mno, nil, abc}; 484 false -> 485 case Jkl of 486 true -> {jkl, nil, nil}; 487 false -> error 488 end 489 end 490 end 491 end. 492 493maps_2b(#{}=Map) -> 494 case case is_map_key(abc, Map) of 495 false -> false; 496 _ -> is_map_key(def, Map) 497 end of 498 true -> 499 {def, ghi, abc}; 500 false -> 501 case case is_map_key(jkl, Map) of 502 false -> false; 503 _ -> is_map_key(def, Map) 504 end of 505 true -> 506 {def, ghi, jkl}; 507 false -> 508 case case is_map_key(abc, Map) of 509 false -> false; 510 _ -> is_map_key(mno, Map) 511 end of 512 true -> 513 {mno, nil, abc}; 514 false -> 515 case is_map_key(jkl, Map) of 516 true -> {jkl, nil, nil}; 517 false -> error 518 end 519 end 520 end 521 end. 522 523-record(wx_ref, {type=any_type,ref=any_ref}). 524 525cover_ssa_dead(_Config) -> 526 str = format_str(str, escapable, [], true), 527 [iolist,str] = format_str(str, escapable, iolist, true), 528 bad = format_str(str, not_escapable, [], true), 529 bad = format_str(str, not_escapable, iolist, true), 530 bad = format_str(str, escapable, [], false), 531 bad = format_str(str, escapable, [], bad), 532 533 DefWxRef = #wx_ref{}, 534 {DefWxRef,77,9999,[]} = contains(#wx_ref{}, 77, 9999), 535 {DefWxRef,77.0,9999,[]} = contains(#wx_ref{}, 77.0, 9999), 536 {DefWxRef,77,9999.0,[]} = contains(#wx_ref{}, 77, 9999.0), 537 {DefWxRef,77.0,9999.0,[]} = contains(#wx_ref{}, 77.0, 9999.0), 538 {any_type,any_ref,42,43,[option]} = contains(#wx_ref{}, {42,43}, [option]), 539 {any_type,any_ref,42,43,[]} = contains(#wx_ref{}, {42,43}, []), 540 {any_type,any_ref,42.0,43,[]} = contains(#wx_ref{}, {42.0,43}, []), 541 {any_type,any_ref,42,43.0,[]} = contains(#wx_ref{}, {42,43.0}, []), 542 {any_type,any_ref,42.0,43.0,[]} = contains(#wx_ref{}, {42.0,43.0}, []), 543 544 nope = conv_alub(false, '=:='), 545 ok = conv_alub(true, '=:='), 546 ok = conv_alub(true, none), 547 error = conv_alub(false, none), 548 549 {false,false} = eval_alu(false, false, false), 550 {true,false} = eval_alu(false, false, true), 551 {false,true} = eval_alu(false, true, false), 552 {false,false} = eval_alu(false, true, true), 553 {false,true} = eval_alu(true, false, false), 554 {false,false} = eval_alu(true, false, true), 555 {true,true} = eval_alu(true, true, false), 556 {false,true} = eval_alu(true, true, true), 557 558 100.0 = percentage(1.0, 0.0), 559 100.0 = percentage(1, 0), 560 0.0 = percentage(0, 0), 561 0.0 = percentage(0.0, 0.0), 562 40.0 = percentage(4.0, 10.0), 563 60.0 = percentage(6, 10), 564 565 {'EXIT',{{badmatch,42},_}} = (catch #{key => abs(("a" = id(42)) /= teacher)}), 566 567 <<>> = id(<< V || V <- [], V andalso false >>), 568 569 false = id(([] = id([])) =/= []), 570 571 ok. 572 573format_str(Str, FormatData, IoList, EscChars) -> 574 Escapable = FormatData =:= escapable, 575 case id(Str) of 576 IoStr when Escapable, EscChars, IoList == [] -> 577 id(IoStr); 578 IoStr when Escapable, EscChars -> 579 [IoList,id(IoStr)]; 580 _ -> 581 bad 582 end. 583 584contains(This, X, Y) when is_record(This, wx_ref), is_number(X), is_number(Y) -> 585 {This,X,Y,[]}; 586contains(#wx_ref{type=ThisT,ref=ThisRef}, {CX,CY}, Options) 587 when is_number(CX), is_number(CY), is_list(Options) -> 588 {ThisT,ThisRef,CX,CY,Options}. 589 590conv_alub(HasDst, CmpOp) -> 591 case (not HasDst) andalso CmpOp =/= none of 592 true -> nope; 593 false -> 594 case HasDst of 595 false -> error; 596 true -> ok 597 end 598 end. 599 600eval_alu(Sign1, Sign2, N) -> 601 V = (Sign1 andalso Sign2 andalso (not N)) 602 or ((not Sign1) andalso (not Sign2) andalso N), 603 C = (Sign1 andalso Sign2) 604 or ((not N) andalso (Sign1 orelse Sign2)), 605 {V,C}. 606 607percentage(Divident, Divisor) -> 608 if Divisor == 0 andalso Divident /= 0 -> 609 100.0; 610 Divisor == 0 -> 611 0.0; 612 true -> 613 Divident / Divisor * 100 614 end. 615 616combine_sw(_Config) -> 617 [a] = do_comb_sw_1(a), 618 [b,b] = do_comb_sw_1(b), 619 [c] = do_comb_sw_1(c), 620 [c] = do_comb_sw_1(c), 621 [] = do_comb_sw_1(z), 622 623 [a] = do_comb_sw_2(a), 624 [b2,b1] = do_comb_sw_2(b), 625 [c] = do_comb_sw_2(c), 626 [c] = do_comb_sw_2(c), 627 [] = do_comb_sw_2(z), 628 629 ok. 630 631do_comb_sw_1(X) -> 632 put(?MODULE, []), 633 if 634 X == a; X == b -> 635 put(?MODULE, [X|get(?MODULE)]); 636 true -> 637 ok 638 end, 639 if 640 X == b; X == c -> 641 put(?MODULE, [X|get(?MODULE)]); 642 true -> 643 ok 644 end, 645 erase(?MODULE). 646 647do_comb_sw_2(X) -> 648 put(?MODULE, []), 649 case X of 650 a -> 651 put(?MODULE, [a|get(?MODULE)]); 652 b -> 653 put(?MODULE, [b1|get(?MODULE)]); 654 _ -> 655 ok 656 end, 657 case X of 658 b -> 659 put(?MODULE, [b2|get(?MODULE)]); 660 c -> 661 put(?MODULE, [c|get(?MODULE)]); 662 _ -> 663 ok 664 end, 665 erase(?MODULE). 666 667share_opt(_Config) -> 668 ok = do_share_opt_1(0), 669 ok = do_share_opt_2(), 670 ok. 671 672do_share_opt_1(A) -> 673 %% The compiler would be stuck in an infinite loop in beam_ssa_share. 674 case A of 675 0 -> a; 676 1 -> b; 677 2 -> c 678 end, 679 receive after 1 -> ok end. 680 681do_share_opt_2() -> 682 ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok), 683 ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok), 684 ok = sopt_2({[], []}, ok), 685 ok. 686 687sopt_2({Flags, Opts}, ok) -> 688 Broadcast = lists:member(broadcast, Flags), 689 P2P = lists:member(pointtopoint, Flags), 690 case Opts of 691 %% The following two clauses would be combined to one, silently 692 %% discarding the guard test of the P2P variable. 693 [{broadaddr,_}|Os] when Broadcast -> 694 sopt_2({Flags, Os}, ok); 695 [{dstaddr,_}|Os] when P2P -> 696 sopt_2({Flags, Os}, ok); 697 [] -> 698 ok 699 end. 700 701beam_ssa_dead_crash(_Config) -> 702 not_A_B = do_beam_ssa_dead_crash(id(false), id(true)), 703 not_A_not_B = do_beam_ssa_dead_crash(false, false), 704 neither = do_beam_ssa_dead_crash(true, false), 705 neither = do_beam_ssa_dead_crash(true, true), 706 ok. 707 708do_beam_ssa_dead_crash(A, B) -> 709 %% beam_ssa_dead attempts to shortcut branches that branch other 710 %% branches. When a two-way branch is encountered, beam_ssa_dead 711 %% will simulate execution along both paths, in the hope that both 712 %% paths happens to end up in the same place. 713 %% 714 %% During the simulated execution of this function, the boolean 715 %% varible for a `br` instruction would be replaced with the 716 %% literal atom `nil`, which is not allowed, and would crash the 717 %% compiler. In practice, during the actual execution, control 718 %% would never be transferred to that `br` instruction when the 719 %% variable in question had the value `nil`. 720 %% 721 %% beam_ssa_dead has been updated to immediately abort the search 722 %% along the current path if there is an attempt to substitute a 723 %% non-boolean value into a `br` instruction. 724 725 case 726 case not A of 727 false -> 728 false; 729 true -> 730 B 731 end 732 of 733 V 734 when 735 V /= nil 736 andalso 737 V /= false -> 738 not_A_B; 739 _ -> 740 case 741 case not A of 742 false -> 743 false; 744 true -> 745 not B 746 end 747 of 748 true -> 749 not_A_not_B; 750 false -> 751 neither 752 end 753 end. 754 755stack_init(_Config) -> 756 6 = stack_init(a, #{a => [1,2,3]}), 757 0 = stack_init(missing, #{}), 758 ok. 759 760stack_init(Key, Map) -> 761 %% beam_ssa_codegen would wrongly assume that y(0) would always be 762 %% initialized by the `get_map_elements` instruction that follows, and 763 %% would set up the stack frame using an `allocate` instruction and 764 %% would not generate an `init` instruction to initialize y(0). 765 Res = case Map of 766 #{Key := Elements} -> 767 %% Elements will be assigned to y(0) if the key Key exists. 768 lists:foldl(fun(El, Acc) -> 769 Acc + El 770 end, 0, Elements); 771 #{} -> 772 %% y(0) will be left uninitialized when the key is not 773 %% present in the map. 774 0 775 end, 776 %% y(0) would be uninitialized here if the key was not present in the map 777 %% (if the second clause was executed). 778 id(Res). 779 780%% Test that compiler "optimizations" don't rewrite mapfold/3 to the 781%% equivalent of slow_mapfoldl/3. 782mapfoldl() -> 783 {N,Size} = mapfoldl_limits(), 784 {Time,_} = timer:tc(fun() -> 785 mapfoldl(fun(Sz, _) -> 786 erlang:garbage_collect(), 787 {Sz,erlang:make_tuple(Sz, a)} 788 end, [], [Size]) 789 end), 790 Seconds = 15 + ceil(10 * Time * N / 1_000_000), 791 io:format("~p seconds timetrap\n", [Seconds]), 792 [{timetrap,{seconds,Seconds}}]. 793 794mapfoldl(_Config) -> 795 test_mapfoldl_implementations(), 796 F = fun(Sz, _) -> 797 erlang:garbage_collect(), 798 {Sz,erlang:make_tuple(Sz, a)} 799 end, 800 {N,Size} = mapfoldl_limits(), 801 List = lists:duplicate(N, Size), 802 {List,Tuple} = mapfoldl(F, [], List), 803 {List,Tuple} = fast_mapfoldl(F, [], List), 804 Size = tuple_size(Tuple), 805 ok. 806 807mapfoldl_limits() -> 808 {1_000,100_000}. 809 810test_mapfoldl_implementations() -> 811 Seq = lists:seq(1, 10), 812 F = fun(N, Sum) -> {N,Sum+N} end, 813 {Seq,55} = mapfoldl(F, 0, Seq), 814 {Seq,55} = fast_mapfoldl(F, 0, Seq), 815 {Seq,55} = slow_mapfoldl(F, 0, Seq), 816 ok. 817 818mapfoldl(F, Acc0, [Hd|Tail]) -> 819 {R,Acc1} = F(Hd, Acc0), 820 {Rs,Acc2} = mapfoldl(F, Acc1, Tail), 821 {[R|Rs],Acc2}; 822mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. 823 824%% Here is an illustration of how the compiler used to sink 825%% get_tuple_element instructions in a way that would cause all 826%% versions of the accumulator to be kept until the end. The compiler 827%% now uses a heuristic to only sink get_tuple_element instructions if 828%% that would cause fewer values to be saved in the stack frame. 829slow_mapfoldl(F, Acc0, [Hd|Tail]) -> 830 Res1 = F(Hd, Acc0), 831 %% By saving the Res1 tuple, all intermediate accumulators will be 832 %% kept to the end. 833 Res2 = slow_mapfoldl(F, element(2, Res1), Tail), 834 {[element(1, Res1)|element(1, Res2)],element(2, Res2)}; 835slow_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. 836 837%% Here is an illustration how the compiler should compile mapfoldl/3 838%% to avoid keeping all intermediate accumulators. Note that 839%% slow_mapfoldl/3 and fast_mapfoldl/3 use the same amount of stack 840%% space. 841fast_mapfoldl(F, Acc0, [Hd|Tail]) -> 842 Res1 = F(Hd, Acc0), 843 R = element(1, Res1), 844 Res2 = fast_mapfoldl(F, element(2, Res1), Tail), 845 {[R|element(1, Res2)],element(2, Res2)}; 846fast_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. 847 848grab_bag(_Config) -> 849 {'EXIT',_} = (catch grab_bag_1()), 850 {'EXIT',_} = (catch grab_bag_2()), 851 {'EXIT',_} = (catch grab_bag_3()), 852 {'EXIT',_} = (catch grab_bag_4()), 853 {'EXIT',{function_clause,[{?MODULE,grab_bag_5,[a,17],_}|_]}} = 854 (catch grab_bag_5(a, 17)), 855 way = grab_bag_6(face), 856 no_match = grab_bag_6("ABC"), 857 no_match = grab_bag_6(any), 858 ok = grab_bag_7(), 859 [] = grab_bag_8(), 860 ok = grab_bag_9(), 861 whatever = grab_bag_10(ignore, whatever), 862 other = grab_bag_11(), 863 {'EXIT',_} = (catch grab_bag_12()), 864 {'EXIT',{{badmatch,[]},_}} = (catch grab_bag_13()), 865 timeout = grab_bag_14(), 866 ?MODULE = grab_bag_15(?MODULE), 867 868 error = grab_bag_16a(timeout_value), 869 {'EXIT',{timeout_value,_}} = (catch grab_bag_16a(whatever)), 870 {'EXIT',{timeout_value,_}} = (catch grab_bag_16b(whatever)), 871 timeout_value = grab_bag_16b(error), 872 873 fact = grab_bag_17(), 874 875 ok. 876 877grab_bag_1() -> 878 %% beam_kernel_to_ssa would crash when attempting to translate a make_fun 879 %% instruction without a destination variable. 880 (catch fun () -> 15 end)(true#{}). 881 882grab_bag_2() -> 883 %% is_guard_cg_safe/1 will be called with #cg_unreachable{}, which was 884 %% not handled. 885 27 886 or 887 try 888 try 889 x#{} 890 catch 891 _:_ -> 892 [] 893 end 894 after 895 false 896 end. 897 898grab_bag_3() -> 899 case 900 fun (V0) 901 when 902 %% The only thing left after optimizations would be 903 %% a bs_add instruction not followed by succeeded, 904 %% which would crash beam_ssa_codegen because there 905 %% was no failure label available. 906 binary_part(<<>>, 907 <<V0:V0/unit:196>>) -> 908 [] 909 end 910 of 911 <<>> -> 912 [] 913 end. 914 915grab_bag_4() -> 916 %% beam_kernel_to_ssa would crash because there was a #cg_phi{} 917 %% instruction that was not referenced from any #cg_break{}. 918 case $f of 919 V0 -> 920 try 921 try fy of 922 V0 -> 923 fu 924 catch 925 throw:$s -> 926 fy 927 end 928 catch 929 error:#{#{[] + [] => []} := false} when [] -> 930 fy 931 after 932 ok 933 end 934 end. 935 936grab_bag_5(A, B) when <<business:(node(power))>> -> 937 true. 938 939grab_bag_6(face) -> 940 way; 941grab_bag_6("ABC") when (node([]))#{size(door) => $k} -> 942 false; 943grab_bag_6(_) -> 944 no_match. 945 946grab_bag_7() -> 947 catch 948 case 949 case 1.6 of 950 %% The hd([] call will be translated to erlang:error(badarg). 951 %% This case exports two variables in Core Erlang (the 952 %% return value of the case and V). beam_kernel_to_ssa was not 953 %% prepared to handle a call to error/1 which is supposed to 954 %% export two variables. 955 <<0.5:(hd([])),V:false>> -> 956 ok 957 end 958 of 959 _ -> 960 V 961 end, 962 ok. 963 964%% ssa_opt_sink would crash if sys_core_fold had not been run. 965grab_bag_8() -> 966 try 967 [] 968 catch 969 _:_ -> 970 try 971 [] 972 catch 973 _:any:_ -> 974 a 975 end; 976 _:right -> 977 b 978 end. 979 980%% The ssa_opt_try optimization would leave a succeeded:body 981%% instruction followed by a #b_ret{} terminator, which would crash 982%% beam_ssa_pre_codegen. 983grab_bag_9() -> 984 catch 985 <<1 || 99, [] <- hour>> bsr false, 986 ok. 987 988grab_bag_10(_, V) -> 989 %% This function needs a stack frame in order to preserve V. 990 fun() -> ok end, 991 V. 992 993grab_bag_11() -> 994 try 0 of 995 false -> error; 996 true -> ok; 997 _ -> other 998 catch 999 _:_ -> 1000 catched 1001 end. 1002 1003grab_bag_12() -> 1004 %% beam_ssa_pre_codegen would try to place the created map in x1. 1005 %% That would not be safe because x0 is not initialized. 1006 check_process_code(1, (#{})#{key := teacher}), 1007 ok. 1008 1009grab_bag_13() -> 1010 %% If sys_core_fold was skipped, beam_ssa_beam would leave 1011 %% unreachable code with invalid phi nodes. 1012 case <<810:true>> = [] of 1013 <<709:false>> -> 1014 ok; 1015 whatever -> 1016 case 42 of 1017 175 -> 1018 {ok,case "b" of 1019 $X -> time 1020 end} 1021 end 1022 end. 1023 1024grab_bag_14() -> 1025 %% If optimizations were turned off, beam_ssa_pre_codegen would 1026 %% sanitize the binary construction instruction, replacing it with 1027 %% a call to erlang:error/1, which is not allowed in a receive. 1028 receive 1029 #{<<42:(-1)>> := _} -> 1030 ok 1031 after 0 -> 1032 timeout 1033 end. 1034 1035grab_bag_15(V) -> 1036 %% Instead of: 1037 %% 1038 %% move x0, y0 1039 %% move y0, x0 1040 %% 1041 %% a swap instruction would be emitted by beam_ssa_codegen: 1042 %% 1043 %% swap x0, y0 1044 %% 1045 case [] of 1046 [] -> V 1047 end:all(), 1048 V. 1049 1050grab_bag_16a(V) -> 1051 try 1052 catch 22, 1053 receive 1054 after bad -> 1055 not_reached 1056 end 1057 catch 1058 _:V -> 1059 error 1060 end. 1061 1062grab_bag_16b(V) -> 1063 try 1064 receive 1065 after get() -> 1066 ok 1067 end 1068 catch 1069 V:Reason -> 1070 Reason 1071 end. 1072 1073grab_bag_17() -> 1074 try "xwCl" of 1075 V when V -> 1076 <<[] || V>>; 1077 [_|_] -> 1078 %% Constant propagation in beam_ssa_codegen:prefer_xregs/2 1079 %% would produce get_hd and get_tl instructions with literal 1080 %% operands. 1081 fact 1082 catch 1083 _:_ -> 1084 [] 1085 end. 1086 1087 1088coverage(_Config) -> 1089 1090 %% Cover beam_ssa_codegen:force_reg/2 1091 no_match = case true of 1092 <<_:42>> -> true; 1093 _ -> no_match 1094 end, 1095 1096 no_match = case [] of 1097 <<$f:1.7>> -> ok; 1098 _ -> no_match 1099 end, 1100 {'EXIT',{{badmatch,$T},_}} = (catch coverage_1()), 1101 1102 error = coverage_2(), 1103 ok = coverage_3(), 1104 1105 ok. 1106 1107coverage_1() -> 1108 <<area/signed-bitstring>> = $T. 1109 1110coverage_2() when << []:<<0/native>> >> -> ok; 1111coverage_2() -> error. 1112 1113coverage_3() -> 1114 %% Cover a line in beam_ssa_pre_codegen:need_frame_1/2. 1115 get(), 1116 ok. 1117 1118%% The identity function. 1119id(I) -> I. 1120