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,grab_bag/1]). 27 28suite() -> [{ct_hooks,[ts_install_cth]}]. 29 30all() -> 31 [{group,p}]. 32 33groups() -> 34 [{p,test_lib:parallel(), 35 [tuple_matching, 36 calls, 37 recv, 38 maps, 39 cover_ssa_dead, 40 combine_sw, 41 share_opt, 42 beam_ssa_dead_crash, 43 stack_init, 44 grab_bag 45 ]}]. 46 47init_per_suite(Config) -> 48 test_lib:recompile(?MODULE), 49 Config. 50 51end_per_suite(_Config) -> 52 ok. 53 54init_per_group(_GroupName, Config) -> 55 Config. 56 57end_per_group(_GroupName, Config) -> 58 Config. 59 60calls(Config) -> 61 Ret = {return,value,Config}, 62 Ret = fun_call(fun(42) -> ok end, Ret), 63 Ret = apply_fun(fun(a, b) -> ok end, [a,b], Ret), 64 Ret = apply_mfa(test_lib, id, [anything], Ret), 65 {'EXIT',{badarg,_}} = (catch call_error()), 66 {'EXIT',{badarg,_}} = (catch call_error(42)), 67 5 = start_it([erlang,length,1,2,3,4,5]), 68 ok. 69 70fun_call(Fun, X0) -> 71 X = id(X0), 72 Fun(42), 73 X. 74 75apply_fun(Fun, Args, X0) -> 76 X = id(X0), 77 apply(Fun, Args), 78 X. 79 80apply_mfa(Mod, Name, Args, X0) -> 81 X = id(X0), 82 apply(Mod, Name, Args), 83 X. 84 85call_error() -> 86 error(badarg), 87 ok. 88 89call_error(I) -> 90 <<I:(-8)>>, 91 ok. 92 93start_it([_|_]=MFA) -> 94 case MFA of 95 [M,F|Args] -> M:F(Args) 96 end. 97 98tuple_matching(_Config) -> 99 do_tuple_matching({tag,42}), 100 101 true = is_two_tuple({a,b}), 102 false = is_two_tuple({a,b,c}), 103 false = is_two_tuple(atom), 104 105 ok. 106 107do_tuple_matching(Arg) -> 108 Res = do_tuple_matching_1(Arg), 109 Res = do_tuple_matching_2(Arg), 110 Res = do_tuple_matching_3(Arg), 111 Res. 112 113do_tuple_matching_1({tag,V}) -> 114 {ok,V}. 115 116do_tuple_matching_2(Tuple) when is_tuple(Tuple) -> 117 Size = tuple_size(Tuple), 118 if 119 Size =:= 2 -> 120 {ok,element(2, Tuple)} 121 end. 122 123do_tuple_matching_3(Tuple) when is_tuple(Tuple) -> 124 Size = tuple_size(Tuple), 125 if 126 Size =:= 2 -> 127 2 = id(Size), 128 {ok,element(2, Tuple)} 129 end. 130 131is_two_tuple(Arg) -> 132 case is_tuple(Arg) of 133 false -> false; 134 true -> tuple_size(Arg) == 2 135 end. 136 137-record(reporter_state, {res,run_config}). 138-record(run_config, {report_interval=0}). 139 140recv(_Config) -> 141 Parent = self(), 142 143 %% Test sync_wait_mon/2. 144 Succ = fun() -> Parent ! {ack,self(),{result,42}} end, 145 {result,42} = sync_wait_mon(spawn_monitor(Succ), infinity), 146 147 Down = fun() -> exit(down) end, 148 {error,down} = sync_wait_mon(spawn_monitor(Down), infinity), 149 150 Exit = fun() -> 151 Self = self(), 152 spawn(fun() -> exit(Self, kill_me) end), 153 receive _ -> ok end 154 end, 155 {error,kill_me} = sync_wait_mon(spawn_monitor(Exit), infinity), 156 157 Timeout = fun() -> receive _ -> ok end end, 158 {error,timeout} = sync_wait_mon(spawn_monitor(Timeout), 0), 159 160 %% Test reporter_loop/1. 161 {a,Parent} = reporter_loop(#reporter_state{res={a,Parent}, 162 run_config=#run_config{}}), 163 164 %% Test bad_sink/0. 165 bad_sink(), 166 167 %% Test tricky_recv_1/0. 168 self() ! 1, 169 a = tricky_recv_1(), 170 self() ! 2, 171 b = tricky_recv_1(), 172 173 %% Test tricky_recv_2/0. 174 self() ! 1, 175 {1,yes} = tricky_recv_2(), 176 self() ! 2, 177 {2,maybe} = tricky_recv_2(), 178 179 %% Test 'receive after infinity' in try/catch. 180 Pid = spawn(fun recv_after_inf_in_try/0), 181 exit(Pid, done), 182 183 %% Test tricky_recv_3(). 184 self() ! {{self(),r0},{1,42,"name"}}, 185 {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_3(), 186 self() ! {{self(),r1},{2,99,<<"data">>}}, 187 {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_3(), 188 189 %% Test tricky_recv_4(). 190 self() ! {[self(),r0],{1,42,"name"}}, 191 {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_4(), 192 self() ! {[self(),r1],{2,99,<<"data">>}}, 193 {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(), 194 195 %% Test tricky_recv_5/0. 196 self() ! 1, 197 a = tricky_recv_5(), 198 self() ! 2, 199 b = tricky_recv_5(), 200 201 %% Test tricky_recv_5a/0. 202 self() ! 1, 203 a = tricky_recv_5a(), 204 self() ! 2, 205 b = tricky_recv_5a(), 206 self() ! any, 207 b = tricky_recv_5a(), 208 209 %% tricky_recv_6/0 is a compile-time error. 210 tricky_recv_6(), 211 212 ok. 213 214sync_wait_mon({Pid, Ref}, Timeout) -> 215 receive 216 {ack,Pid,Return} -> 217 erlang:demonitor(Ref, [flush]), 218 Return; 219 {'DOWN',Ref,_Type,Pid,Reason} -> 220 {error,Reason}; 221 {'EXIT',Pid,Reason} -> 222 erlang:demonitor(Ref, [flush]), 223 {error,Reason} 224 after Timeout -> 225 erlang:demonitor(Ref, [flush]), 226 exit(Pid, kill), 227 {error,timeout} 228 end. 229 230reporter_loop(State) -> 231 RC = State#reporter_state.run_config, 232 receive after RC#run_config.report_interval -> 233 State#reporter_state.res 234 end. 235 236bad_sink() -> 237 {ok,Pid} = my_spawn(self()), 238 %% The get_tuple_element instruction for the matching 239 %% above was sinked into the receive loop. That will 240 %% not work (and would be bad for performance if it 241 %% would work). 242 receive 243 {ok,Pid} -> 244 ok; 245 error -> 246 exit(failed) 247 end, 248 exit(Pid, kill). 249 250my_spawn(Parent) -> 251 Pid = spawn(fun() -> 252 Parent ! {ok,self()}, 253 receive _ -> ok end 254 end), 255 {ok,Pid}. 256 257tricky_recv_1() -> 258 receive 259 X=1 -> 260 id(42), 261 a; 262 X=2 -> 263 b 264 end, 265 case X of 266 1 -> a; 267 2 -> b 268 end. 269 270tricky_recv_2() -> 271 receive 272 X=1 -> 273 Y = case id(X) of 274 1 -> yes; 275 _ -> no 276 end, 277 a; 278 X=2 -> 279 Y = maybe, 280 b 281 end, 282 {X,Y}. 283 284recv_after_inf_in_try() -> 285 try 286 %% Used to crash beam_kernel_to_ssa. 287 receive after infinity -> ok end 288 catch 289 _A:_B -> 290 receive after infinity -> ok end 291 end. 292 293tricky_recv_3() -> 294 {Pid, R, Request} = 295 receive 296 {{Pid0,R0}, {1, Proto0, Name0}} -> 297 {Pid0, R0, 298 [<<1:32, 1:8, Proto0:8>>,Name0,0]}; 299 {{Pid1,R1}, {2, Proto1, Data1}} -> 300 {Pid1, R1, 301 <<1:32, 2:8, Proto1:8, Data1/binary>>} 302 end, 303 id({Pid,R,Request}). 304 305tricky_recv_4() -> 306 {Pid, R, Request} = 307 receive 308 {[Pid0,R0], {1, Proto0, Name0}} -> 309 {Pid0, R0, 310 [<<1:32, 1:8, Proto0:8>>,Name0,0]}; 311 {[Pid1,R1], {2, Proto1, Data1}} -> 312 {Pid1, R1, 313 <<1:32, 2:8, Proto1:8, Data1/binary>>} 314 end, 315 id({Pid,R,Request}). 316 317%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges 318%% when fixing up receives; the call to id/2 can either succeed or land in the 319%% catch block, and we added a phi node to its immediate successor. 320tricky_recv_5() -> 321 try 322 receive 323 X=1 -> 324 id(42), 325 a; 326 X=2 -> 327 b 328 end, 329 case X of 330 1 -> a; 331 2 -> b 332 end 333 catch 334 _:_ -> c 335 end. 336 337%% beam_ssa_pre_codegen would find the wrong exit block when fixing up 338%% receives. 339tricky_recv_5a() -> 340 try 341 receive 342 X=1 -> 343 id(42), 344 a; 345 X=_ -> 346 b 347 end, 348 %% The following is the code in the common exit block. 349 if X =:= 1 -> a; 350 true -> b 351 end 352 catch 353 %% But this code with the landingpad instruction was found, 354 %% because it happened to occur before the true exit block 355 %% in the reverse post order. 356 _:_ -> c 357 end. 358 359 360%% When fixing tricky_recv_5, we introduced a compiler crash when the common 361%% exit block was ?BADARG_BLOCK and floats were in the picture. 362tricky_recv_6() -> 363 RefA = make_ref(), 364 RefB = make_ref(), 365 receive 366 {RefA, Number} -> Number + 1.0; 367 {RefB, Number} -> Number + 2.0 368 after 0 -> 369 ok 370 end. 371 372maps(_Config) -> 373 {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), 374 ok. 375 376maps_1(K) -> 377 _ = id(42), 378 #{K:=V} = #{}, 379 V. 380 381-record(wx_ref, {type=any_type,ref=any_ref}). 382 383cover_ssa_dead(_Config) -> 384 str = format_str(str, escapable, [], true), 385 [iolist,str] = format_str(str, escapable, iolist, true), 386 bad = format_str(str, not_escapable, [], true), 387 bad = format_str(str, not_escapable, iolist, true), 388 bad = format_str(str, escapable, [], false), 389 bad = format_str(str, escapable, [], bad), 390 391 DefWxRef = #wx_ref{}, 392 {DefWxRef,77,9999,[]} = contains(#wx_ref{}, 77, 9999), 393 {DefWxRef,77.0,9999,[]} = contains(#wx_ref{}, 77.0, 9999), 394 {DefWxRef,77,9999.0,[]} = contains(#wx_ref{}, 77, 9999.0), 395 {DefWxRef,77.0,9999.0,[]} = contains(#wx_ref{}, 77.0, 9999.0), 396 {any_type,any_ref,42,43,[option]} = contains(#wx_ref{}, {42,43}, [option]), 397 {any_type,any_ref,42,43,[]} = contains(#wx_ref{}, {42,43}, []), 398 {any_type,any_ref,42.0,43,[]} = contains(#wx_ref{}, {42.0,43}, []), 399 {any_type,any_ref,42,43.0,[]} = contains(#wx_ref{}, {42,43.0}, []), 400 {any_type,any_ref,42.0,43.0,[]} = contains(#wx_ref{}, {42.0,43.0}, []), 401 402 nope = conv_alub(false, '=:='), 403 ok = conv_alub(true, '=:='), 404 ok = conv_alub(true, none), 405 error = conv_alub(false, none), 406 407 {false,false} = eval_alu(false, false, false), 408 {true,false} = eval_alu(false, false, true), 409 {false,true} = eval_alu(false, true, false), 410 {false,false} = eval_alu(false, true, true), 411 {false,true} = eval_alu(true, false, false), 412 {false,false} = eval_alu(true, false, true), 413 {true,true} = eval_alu(true, true, false), 414 {false,true} = eval_alu(true, true, true), 415 416 100.0 = percentage(1.0, 0.0), 417 100.0 = percentage(1, 0), 418 0.0 = percentage(0, 0), 419 0.0 = percentage(0.0, 0.0), 420 40.0 = percentage(4.0, 10.0), 421 60.0 = percentage(6, 10), 422 423 %% Cover '=:=', followed by '=/='. 424 false = 'cover__=:=__=/='(41), 425 true = 'cover__=:=__=/='(42), 426 false = 'cover__=:=__=/='(43), 427 428 %% Cover '<', followed by '=/='. 429 true = 'cover__<__=/='(41), 430 false = 'cover__<__=/='(42), 431 false = 'cover__<__=/='(43), 432 433 %% Cover '=<', followed by '=/='. 434 true = 'cover__=<__=/='(41), 435 true = 'cover__=<__=/='(42), 436 false = 'cover__=<__=/='(43), 437 438 %% Cover '>=', followed by '=/='. 439 false = 'cover__>=__=/='(41), 440 true = 'cover__>=__=/='(42), 441 true = 'cover__>=__=/='(43), 442 443 %% Cover '>', followed by '=/='. 444 false = 'cover__>__=/='(41), 445 false = 'cover__>__=/='(42), 446 true = 'cover__>__=/='(43), 447 448 ok. 449 450'cover__=:=__=/='(X) when X =:= 42 -> X =/= 43; 451'cover__=:=__=/='(_) -> false. 452 453'cover__<__=/='(X) when X < 42 -> X =/= 42; 454'cover__<__=/='(_) -> false. 455 456'cover__=<__=/='(X) when X =< 42 -> X =/= 43; 457'cover__=<__=/='(_) -> false. 458 459'cover__>=__=/='(X) when X >= 42 -> X =/= 41; 460'cover__>=__=/='(_) -> false. 461 462'cover__>__=/='(X) when X > 42 -> X =/= 42; 463'cover__>__=/='(_) -> false. 464 465format_str(Str, FormatData, IoList, EscChars) -> 466 Escapable = FormatData =:= escapable, 467 case id(Str) of 468 IoStr when Escapable, EscChars, IoList == [] -> 469 id(IoStr); 470 IoStr when Escapable, EscChars -> 471 [IoList,id(IoStr)]; 472 _ -> 473 bad 474 end. 475 476contains(This, X, Y) when is_record(This, wx_ref), is_number(X), is_number(Y) -> 477 {This,X,Y,[]}; 478contains(#wx_ref{type=ThisT,ref=ThisRef}, {CX,CY}, Options) 479 when is_number(CX), is_number(CY), is_list(Options) -> 480 {ThisT,ThisRef,CX,CY,Options}. 481 482conv_alub(HasDst, CmpOp) -> 483 case (not HasDst) andalso CmpOp =/= none of 484 true -> nope; 485 false -> 486 case HasDst of 487 false -> error; 488 true -> ok 489 end 490 end. 491 492eval_alu(Sign1, Sign2, N) -> 493 V = (Sign1 andalso Sign2 andalso (not N)) 494 or ((not Sign1) andalso (not Sign2) andalso N), 495 C = (Sign1 andalso Sign2) 496 or ((not N) andalso (Sign1 orelse Sign2)), 497 {V,C}. 498 499percentage(Divident, Divisor) -> 500 if Divisor == 0 andalso Divident /= 0 -> 501 100.0; 502 Divisor == 0 -> 503 0.0; 504 true -> 505 Divident / Divisor * 100 506 end. 507 508combine_sw(_Config) -> 509 [a] = do_comb_sw_1(a), 510 [b,b] = do_comb_sw_1(b), 511 [c] = do_comb_sw_1(c), 512 [c] = do_comb_sw_1(c), 513 [] = do_comb_sw_1(z), 514 515 [a] = do_comb_sw_2(a), 516 [b2,b1] = do_comb_sw_2(b), 517 [c] = do_comb_sw_2(c), 518 [c] = do_comb_sw_2(c), 519 [] = do_comb_sw_2(z), 520 521 ok. 522 523do_comb_sw_1(X) -> 524 put(?MODULE, []), 525 if 526 X == a; X == b -> 527 put(?MODULE, [X|get(?MODULE)]); 528 true -> 529 ok 530 end, 531 if 532 X == b; X == c -> 533 put(?MODULE, [X|get(?MODULE)]); 534 true -> 535 ok 536 end, 537 erase(?MODULE). 538 539do_comb_sw_2(X) -> 540 put(?MODULE, []), 541 case X of 542 a -> 543 put(?MODULE, [a|get(?MODULE)]); 544 b -> 545 put(?MODULE, [b1|get(?MODULE)]); 546 _ -> 547 ok 548 end, 549 case X of 550 b -> 551 put(?MODULE, [b2|get(?MODULE)]); 552 c -> 553 put(?MODULE, [c|get(?MODULE)]); 554 _ -> 555 ok 556 end, 557 erase(?MODULE). 558 559share_opt(_Config) -> 560 ok = do_share_opt_1(0), 561 ok = do_share_opt_2(), 562 ok. 563 564do_share_opt_1(A) -> 565 %% The compiler would be stuck in an infinite loop in beam_ssa_share. 566 case A of 567 0 -> a; 568 1 -> b; 569 2 -> c 570 end, 571 receive after 1 -> ok end. 572 573do_share_opt_2() -> 574 ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok), 575 ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok), 576 ok = sopt_2({[], []}, ok), 577 ok. 578 579sopt_2({Flags, Opts}, ok) -> 580 Broadcast = lists:member(broadcast, Flags), 581 P2P = lists:member(pointtopoint, Flags), 582 case Opts of 583 %% The following two clauses would be combined to one, silently 584 %% discarding the guard test of the P2P variable. 585 [{broadaddr,_}|Os] when Broadcast -> 586 sopt_2({Flags, Os}, ok); 587 [{dstaddr,_}|Os] when P2P -> 588 sopt_2({Flags, Os}, ok); 589 [] -> 590 ok 591 end. 592 593beam_ssa_dead_crash(_Config) -> 594 not_A_B = do_beam_ssa_dead_crash(id(false), id(true)), 595 not_A_not_B = do_beam_ssa_dead_crash(false, false), 596 neither = do_beam_ssa_dead_crash(true, false), 597 neither = do_beam_ssa_dead_crash(true, true), 598 ok. 599 600do_beam_ssa_dead_crash(A, B) -> 601 %% beam_ssa_dead attempts to shortcut branches that branch other 602 %% branches. When a two-way branch is encountered, beam_ssa_dead 603 %% will simulate execution along both paths, in the hope that both 604 %% paths happens to end up in the same place. 605 %% 606 %% During the simulated execution of this function, the boolean 607 %% varible for a `br` instruction would be replaced with the 608 %% literal atom `nil`, which is not allowed, and would crash the 609 %% compiler. In practice, during the actual execution, control 610 %% would never be transferred to that `br` instruction when the 611 %% variable in question had the value `nil`. 612 %% 613 %% beam_ssa_dead has been updated to immediately abort the search 614 %% along the current path if there is an attempt to substitute a 615 %% non-boolean value into a `br` instruction. 616 617 case 618 case not A of 619 false -> 620 false; 621 true -> 622 B 623 end 624 of 625 V 626 when 627 V /= nil 628 andalso 629 V /= false -> 630 not_A_B; 631 _ -> 632 case 633 case not A of 634 false -> 635 false; 636 true -> 637 not B 638 end 639 of 640 true -> 641 not_A_not_B; 642 false -> 643 neither 644 end 645 end. 646 647stack_init(_Config) -> 648 6 = stack_init(a, #{a => [1,2,3]}), 649 0 = stack_init(missing, #{}), 650 ok. 651 652stack_init(Key, Map) -> 653 %% beam_ssa_codegen would wrongly assume that y(0) would always be 654 %% initialized by the `get_map_elements` instruction that follows, and 655 %% would set up the stack frame using an `allocate` instruction and 656 %% would not generate an `init` instruction to initialize y(0). 657 Res = case Map of 658 #{Key := Elements} -> 659 %% Elements will be assigned to y(0) if the key Key exists. 660 lists:foldl(fun(El, Acc) -> 661 Acc + El 662 end, 0, Elements); 663 #{} -> 664 %% y(0) will be left uninitialized when the key is not 665 %% present in the map. 666 0 667 end, 668 %% y(0) would be uninitialized here if the key was not present in the map 669 %% (if the second clause was executed). 670 id(Res). 671 672grab_bag(_Config) -> 673 {'EXIT',_} = (catch grab_bag_3()), 674 ok. 675 676grab_bag_3() -> 677 case 678 fun (V0) 679 when 680 %% The only thing left after optimizations would be 681 %% a bs_add instruction not followed by succeeded, 682 %% which would crash beam_ssa_codegen because there 683 %% was no failure label available. 684 binary_part(<<>>, 685 <<V0:V0/unit:196>>) -> 686 [] 687 end 688 of 689 <<>> -> 690 [] 691 end. 692 693 694%% The identity function. 695id(I) -> I. 696