# # %CopyrightBegin% # # Copyright Ericsson AB 1997-2017. 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% # # # The instructions that follows are only known by the loader and the emulator. # They can be changed without recompiling old Beam files. # # Instructions starting with a "i_" prefix are instructions produced by # instruction transformations; thus, they never occur in BEAM files. # # The too_old_compiler/0 instruction is specially handled in beam_load.c # to produce a user-friendly message informing the user that the module # needs to be re-compiled with a modern compiler. too_old_compiler/0 too_old_compiler | never() => # In R9C and earlier, the loader used to insert special instructions inside # the module_info/0,1 functions. (In R10B and later, the compiler inserts # an explicit call to an undocumented BIF, so that no loader trickery is # necessary.) Since the instructions don't work correctly in R12B, simply # refuse to load the module. func_info M=a a==am_module_info A=u==0 | label L | move n x==0 => too_old_compiler func_info M=a a==am_module_info A=u==1 | label L | move n x==0 => too_old_compiler # The undocumented and unsupported guard BIF is_constant/1 was removed # in R13. The is_constant/2 operation is marked as obsolete in genop.tab, # so the loader will automatically generate a too_old_compiler message # it is used, but we need to handle the is_constant/1 BIF specially here. bif1 Fail u$func:erlang:is_constant/1 Src Dst => too_old_compiler # Since the constant pool was introduced in R12B, empty tuples ({}) # are literals. Therefore we no longer need to allow put_tuple/2 # with a tuple size of zero. put_tuple u==0 d => too_old_compiler # # All the other instructions. # %cold label L i_func_info I a a I int_code_end i_generic_breakpoint i_debug_breakpoint i_return_time_trace i_return_to_trace i_yield trace_jump W %hot return # # To ensure that a "move Src x(0)" instruction can be combined # with the following call instruction, we need to make sure that # there is no line/1 instruction between the move and the call. # # A tail-recursive call to an external function (non-BIF) will # never be saved on the stack, so there is no reason to keep # the line instruction. (The compiler did not remove the line # instruction because it cannot tell the difference between # BIFs and ordinary Erlang functions.) # move S X0=x==0 | line Loc | call_ext Ar Func => \ line Loc | move S X0 | call_ext Ar Func move S X0=x==0 | line Loc | call_ext_last Ar Func=u$is_not_bif D => \ move S X0 | call_ext_last Ar Func D move S X0=x==0 | line Loc | call_ext_only Ar Func=u$is_not_bif => \ move S X0 | call_ext_only Ar Func move S X0=x==0 | line Loc | call Ar Func => \ line Loc | move S X0 | call Ar Func line Loc | func_info M F A => func_info M F A | line Loc line I allocate t t? allocate_heap t I t? %cold deallocate Q %hot init y allocate_zero t t? allocate_heap_zero t I t? trim N Remaining => i_trim N i_trim t test_heap I t? allocate_heap S u==0 R => allocate S R allocate_heap_zero S u==0 R => allocate_zero S R init2 y y init3 y y y init Y1 | init Y2 | init Y3 => init3 Y1 Y2 Y3 init Y1 | init Y2 => init2 Y1 Y2 # Selecting values select_val S=aiq Fail=f Size=u Rest=* => const_select_val(S, Fail, Size, Rest) select_val S=s Fail=f Size=u Rest=* | use_jump_tab(Size, Rest) => \ gen_jump_tab(S, Fail, Size, Rest) is_integer Fail=f S | select_val S=s Fail=f Size=u Rest=* | use_jump_tab(Size, Rest) => \ gen_jump_tab(S, Fail, Size, Rest) is_integer TypeFail=f S | select_val S=s Fail=f Size=u Rest=* | \ mixed_types(Size, Rest) => \ gen_split_values(S, TypeFail, Fail, Size, Rest) select_val S=s Fail=f Size=u Rest=* | mixed_types(Size, Rest) => \ gen_split_values(S, Fail, Fail, Size, Rest) is_integer Fail=f S | select_val S=d Fail=f Size=u Rest=* | \ fixed_size_values(Size, Rest) => gen_select_val(S, Fail, Size, Rest) is_atom Fail=f S | select_val S=d Fail=f Size=u Rest=* | \ fixed_size_values(Size, Rest) => gen_select_val(S, Fail, Size, Rest) select_val S=s Fail=f Size=u Rest=* | floats_or_bignums(Size, Rest) => \ gen_select_literals(S, Fail, Size, Rest) select_val S=d Fail=f Size=u Rest=* | fixed_size_values(Size, Rest) => \ gen_select_val(S, Fail, Size, Rest) is_tuple Fail=f S | select_tuple_arity S=d Fail=f Size=u Rest=* => \ gen_select_tuple_arity(S, Fail, Size, Rest) select_tuple_arity S=d Fail=f Size=u Rest=* => \ gen_select_tuple_arity(S, Fail, Size, Rest) i_select_val_bins xy f? I i_select_val_lins xy f? I i_select_val2 xy f? c c i_select_tuple_arity xy f? I i_select_tuple_arity2 xy f? A A i_jump_on_val_zero xy f? I i_jump_on_val xy f? I W get_list xy xy xy # The following get_list instructions using x(0) are frequently used. get_list r x x get_list r r y get_list x r x get_list r x y get_list r y r get_list r x r # Old-style catch. catch y f catch_end y # Try/catch. try Y F => catch Y F try_case y try_end y %cold try_case_end s %hot # Destructive set tuple element set_tuple_element s S P # Get tuple element i_get_tuple_element xy P x %cold i_get_tuple_element xy P y %hot i_get_tuple_element2 x P x i_get_tuple_element2y x P y y i_get_tuple_element3 x P x %cold is_number f? xy %hot is_number Fail=f i => is_number Fail=f na => jump Fail is_number Fail Literal=q => move Literal x | is_number Fail x jump f # # Expection rasing instructions. Infrequently executed. # %cold case_end NotInX=cy => move NotInX x | case_end x badmatch NotInX=cy => move NotInX x | badmatch x case_end x badmatch x if_end # Operands for raise/2 are almost always in x(2) and x(1). # Optimize for that case. raise x==2 x==1 => i_raise raise Trace=y Value=y => move Trace x=2 | move Value x=1 | i_raise raise Trace Value => move Trace x=3 | move Value x=1 | move x=3 x=2 | i_raise i_raise # Internal now, but could be useful to make known to the compiler. badarg j system_limit j %hot # # Move instructions. # move C=cxy x==0 | jump Lbl => move_jump Lbl C move_jump f ncxy # Movement to and from the stack is common # Try to pack as much as we can into one instruction # Window move move_window/5 move_window/6 # x -> y move X1=x Y1=y | move X2=x Y2=y | move X3=x Y3=y | succ(Y1,Y2) | succ(Y2,Y3) => \ move_window X1 X2 X3 Y1 Y3 move_window X1=x X2=x X3=x Y1=y Y3=y | move X4=x Y4=y | succ(Y3,Y4) => \ move_window X1 X2 X3 X4 Y1 Y4 move_window X1=x X2=x X3=x X4=x Y1=y Y4=y | move X5=x Y5=y | succ(Y4,Y5) => \ move_window5 X1 X2 X3 X4 X5 Y1 move_window X1=x X2=x X3=x Y1=y Y3=y => move_window3 X1 X2 X3 Y1 move_window X1=x X2=x X3=x X4=x Y1=y Y4=y => move_window4 X1 X2 X3 X4 Y1 move_window3 x x x y move_window4 x x x x y move_window5 x x x x x y # Swap registers. move R1=x Tmp=x | move R2=xy R1 | move Tmp R2 => swap_temp R1 R2 Tmp swap_temp R1 R2 Tmp | line Loc | apply Live | is_killed_apply(Tmp, Live) => \ swap R1 R2 | line Loc | apply Live swap_temp R1 R2 Tmp | line Loc | call Live Addr | is_killed(Tmp, Live) => \ swap R1 R2 | line Loc | call Live Addr swap_temp R1 R2 Tmp | call_only Live Addr | \ is_killed(Tmp, Live) => swap R1 R2 | call_only Live Addr swap_temp R1 R2 Tmp | call_last Live Addr D | \ is_killed(Tmp, Live) => swap R1 R2 | call_last Live Addr D swap_temp R1 R2 Tmp | line Loc | call_ext Live Addr | is_killed(Tmp, Live) => \ swap R1 R2 | line Loc | call_ext Live Addr swap_temp R1 R2 Tmp | line Loc | call_ext_only Live Addr | \ is_killed(Tmp, Live) => swap R1 R2 | line Loc | call_ext_only Live Addr swap_temp R1 R2 Tmp | line Loc | call_ext_last Live Addr D | \ is_killed(Tmp, Live) => swap R1 R2 | line Loc | call_ext_last Live Addr D swap_temp x xy x swap x xy move Src=x D1=x | move Src=x D2=x => move_dup Src D1 D2 move Src=x SD=x | move SD=x D=x => move_dup Src SD D move Src=x D1=x | move Src=x D2=y => move_dup Src D1 D2 move Src=y SD=x | move SD=x D=y => move_dup Src SD D move Src=x SD=x | move SD=x D=y => move_dup Src SD D move Src=y SD=x | move SD=x D=x => move_dup Src SD D move SD=x D=x | move Src=xy SD=x => move_shift Src SD D move SD=y D=x | move Src=x SD=y => move_shift Src SD D move SD=x D=y | move Src=x SD=x => move_shift Src SD D # The transformations above guarantee that the source for # the second move is not the same as the destination for # the first move. That means that we can do the moves in # parallel (fetch both values, then store them) which could # be faster. move X1=x Y1=y | move X2=x Y2=y => move2_par X1 Y1 X2 Y2 move Y1=y X1=x | move Y2=y X2=x => move2_par Y1 X1 Y2 X2 move X1=x X2=x | move X3=x X4=x => move2_par X1 X2 X3 X4 move X1=x X2=x | move X3=x Y1=y => move2_par X1 X2 X3 Y1 move S1=x S2=x | move X1=x Y1=y => move2_par S1 S2 X1 Y1 move S1=y S2=x | move X1=x Y1=y => move2_par S1 S2 X1 Y1 move Y1=y X1=x | move S1=x D1=x => move2_par Y1 X1 S1 D1 move S1=x D1=x | move Y1=y X1=x => move2_par S1 D1 Y1 X1 move2_par X1=x Y1=y X2=x Y2=y | move X3=x Y3=y => move3 X1 Y1 X2 Y2 X3 Y3 move2_par Y1=y X1=x Y2=y X2=x | move Y3=y X3=x => move3 Y1 X1 Y2 X2 Y3 X3 move2_par X1=x X2=x X3=x X4=x | move X5=x X6=x => move3 X1 X2 X3 X4 X5 X6 move C=aiq X=x==1 => move_x1 C move C=aiq X=x==2 => move_x2 C move_x1 c move_x2 c move_shift x x x move_shift y x x move_shift x y x move_shift x x y move_dup xy x xy move2_par x y x y move2_par y x y x move2_par x x x x move2_par x x x y move2_par y x x y move2_par x x y x move2_par y x x x move3 x y x y x y move3 y x y x y x move3 x x x x x x # The compiler almost never generates a "move Literal y(Y)" instruction, # so let's cheat if we encounter one. move S=n D=y => init D move S=c D=y => move S x | move x D move x x move x y move y x move c x move n x move y y # The following move instructions using x(0) are frequently used. move x r move r x move y r move c r move r y # Receive operations. loop_rec Fail x==0 | smp_mark_target_label(Fail) => i_loop_rec Fail label L | wait_timeout Fail Src | smp_already_locked(L) => \ label L | wait_timeout_locked Src Fail wait_timeout Fail Src => wait_timeout_unlocked Src Fail wait_timeout_unlocked Src=aiq Fail => gen_literal_timeout(Fail, Src) wait_timeout_locked Src=aiq Fail => gen_literal_timeout_locked(Fail, Src) label L | wait Fail | smp_already_locked(L) => label L | wait_locked Fail wait Fail => wait_unlocked Fail label L | timeout | smp_already_locked(L) => label L | timeout_locked remove_message timeout timeout_locked i_loop_rec f loop_rec_end f wait_locked f wait_unlocked f # Note that a timeout value must fit in 32 bits. wait_timeout_unlocked_int I f wait_timeout_unlocked s f wait_timeout_locked_int I f wait_timeout_locked s f %cold i_wait_error i_wait_error_locked %hot send # # Optimized comparisons with one immediate/literal operand. # is_eq_exact Lbl S S => is_eq_exact Lbl C1=c C2=c => move C1 x | is_eq_exact Lbl x C2 is_eq_exact Lbl C=c R=xy => is_eq_exact Lbl R C is_eq_exact Lbl R=xy n => is_nil Lbl R is_eq_exact Lbl R=xy C=ia => i_is_eq_exact_immed Lbl R C is_eq_exact Lbl R=xy C=q => i_is_eq_exact_literal Lbl R C is_ne_exact Lbl S S => jump Lbl is_ne_exact Lbl C1=c C2=c => move C1 x | is_ne_exact Lbl x C2 is_ne_exact Lbl C=c R=xy => is_ne_exact Lbl R C is_ne_exact Lbl R=xy C=ian => i_is_ne_exact_immed Lbl R C is_ne_exact Lbl R=xy C=q => i_is_ne_exact_literal Lbl R C i_is_eq_exact_immed f? rxy c i_is_eq_exact_literal f? xy c i_is_ne_exact_immed f? xy c i_is_ne_exact_literal f? xy c is_eq_exact Lbl Y=y X=x => is_eq_exact Lbl X Y is_eq_exact f? x xy is_eq_exact f? y y is_ne_exact f? S S is_lt f? x x is_lt f? x c is_lt f? c x %cold is_lt f? s s %hot is_ge f? x x is_ge f? x c is_ge f? c x %cold is_ge f? s s %hot is_eq f? s s is_ne f? s s # # Putting things. # put_tuple Arity Dst => i_put_tuple Dst u i_put_tuple Dst Arity Puts=* | put S1 | put S2 | \ put S3 | put S4 | put S5 => \ tuple_append_put5(Arity, Dst, Puts, S1, S2, S3, S4, S5) i_put_tuple Dst Arity Puts=* | put S => \ tuple_append_put(Arity, Dst, Puts, S) i_put_tuple/2 i_put_tuple xy I # # The instruction "put_list Const [] Dst" were generated in rare # circumstances up to and including OTP 18. Starting with OTP 19, # AFAIK, it should never be generated. # put_list Const=c n Dst => move Const x | put_list x n Dst put_list Src Dst=x Dst => update_list Src Dst update_list xyc x put_list x n x put_list y n x put_list x x x put_list y x x put_list y y x put_list x y x # put_list SrcReg Constant Dst put_list x c x put_list x c y put_list y c x # put_list Constant SrcReg Dst put_list c x x put_list c y x # The following put_list instructions using x(0) are frequently used. put_list r n r put_list r n x put_list r x x put_list r x r put_list x x r %cold put_list s s d %hot # # Some more only used by the emulator # %cold normal_exit continue_exit apply_bif call_nif call_error_handler error_action_code return_trace %hot # # Instruction transformations & folded instructions. # # Note: There is no 'move_return y r', since there never are any y registers # when we do move_return (if we have y registers, we must do move_deallocate_return). move S x==0 | return => move_return S move_return xcn move S x==0 | deallocate D | return => move_deallocate_return S D move_deallocate_return xycn Q deallocate D | return => deallocate_return D deallocate_return Q test_heap Need u==1 | put_list Y=y x==0 x==0 => test_heap_1_put_list Need Y test_heap_1_put_list I y # # is_tagged_tuple Fail=f Src=rxy Arity Atom=a # is_tagged_tuple Fail Literal=q Arity Atom => \ move Literal x | is_tagged_tuple Fail x Arity Atom is_tagged_tuple Fail=f c Arity Atom => jump Fail is_tagged_tuple f? rxy A a # Test tuple & arity (head) is_tuple Fail Literal=q => move Literal x | is_tuple Fail x is_tuple Fail=f c => jump Fail is_tuple Fail=f S=xy | test_arity Fail=f S=xy Arity => is_tuple_of_arity Fail S Arity is_tuple_of_arity f? rxy A is_tuple f? rxy test_arity Fail Literal=q Arity => move Literal x | test_arity Fail x Arity test_arity Fail=f c Arity => jump Fail test_arity f? xy A get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \ get_tuple_element Reg=x P3 D3=x | \ succ(P1, P2) | succ(P2, P3) | \ succ(D1, D2) | succ(D2, D3) => i_get_tuple_element3 Reg P1 D1 get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \ succ(P1, P2) | succ(D1, D2) => i_get_tuple_element2 Reg P1 D1 get_tuple_element Reg=x P1 D1=y | get_tuple_element Reg=x P2 D2=y | \ succ(P1, P2) => i_get_tuple_element2y Reg P1 D1 D2 get_tuple_element Reg P Dst => i_get_tuple_element Reg P Dst is_integer Fail=f i => is_integer Fail=f an => jump Fail is_integer Fail Literal=q => move Literal x | is_integer Fail x is_integer Fail=f S=x | allocate Need Regs => is_integer_allocate Fail S Need Regs is_integer_allocate f? x t t is_integer f? xy is_list Fail=f n => is_list Fail Literal=q => move Literal x | is_list Fail x is_list Fail=f c => jump Fail is_list f? x %cold is_list f? y %hot is_nonempty_list Fail=f S=x | allocate Need Rs => is_nonempty_list_allocate Fail S Need Rs is_nonempty_list F=f x==0 | test_heap I1 I2 => is_nonempty_list_test_heap F I1 I2 is_nonempty_list Fail=f S=x | get_list S D1=x D2=x => \ is_nonempty_list_get_list Fail S D1 D2 is_nonempty_list_allocate f? rx t t is_nonempty_list_test_heap f? I t is_nonempty_list_get_list f? rx x x is_nonempty_list f? xy is_atom f? x %cold is_atom f? y %hot is_atom Fail=f a => is_atom Fail=f niq => jump Fail is_float f? x %cold is_float f? y %hot is_float Fail=f nai => jump Fail is_float Fail Literal=q => move Literal x | is_float Fail x is_nil Fail=f n => is_nil Fail=f qia => jump Fail is_nil f? xy is_binary Fail Literal=q => move Literal x | is_binary Fail x is_binary Fail=f c => jump Fail is_binary f? x %cold is_binary f? y %hot # XXX Deprecated. is_bitstr Fail Term => is_bitstring Fail Term is_bitstring Fail Literal=q => move Literal x | is_bitstring Fail x is_bitstring Fail=f c => jump Fail is_bitstring f? x %cold is_bitstring f? y %hot is_reference Fail=f cq => jump Fail is_reference f? x %cold is_reference f? y %hot is_pid Fail=f cq => jump Fail is_pid f? x %cold is_pid f? y %hot is_port Fail=f cq => jump Fail is_port f? x %cold is_port f? y %hot is_boolean Fail=f a==am_true => is_boolean Fail=f a==am_false => is_boolean Fail=f ac => jump Fail %cold is_boolean f? xy %hot is_function2 Fail=f acq Arity => jump Fail is_function2 Fail=f Fun a => jump Fail is_function2 f? S s # Allocating & initializing. allocate Need Regs | init Y => allocate_init Need Regs Y init Y1 | init Y2 => init2 Y1 Y2 allocate_init t t? y ################################################################# # External function and bif calls. ################################################################# # # The BIFs erts_internal:check_process_code/1 must be called like a function, # to ensure that c_p->i (program counter) is set correctly (an ordinary # BIF call doesn't set it). # call_ext u==1 Bif=u$bif:erts_internal:check_process_code/1 => i_call_ext Bif call_ext_last u==1 Bif=u$bif:erts_internal:check_process_code/1 D => i_call_ext_last Bif D call_ext_only u==1 Bif=u$bif:erts_internal:check_process_code/1 => i_call_ext_only Bif # # The BIFs erts_internal:garbage_collect/1 must be called like a function, # to allow them to invoke the garbage collector. (The stack pointer must # be saved and p->arity must be zeroed, which is not done on ordinary BIF calls.) # call_ext u==1 Bif=u$bif:erts_internal:garbage_collect/1 => i_call_ext Bif call_ext_last u==1 Bif=u$bif:erts_internal:garbage_collect/1 D => i_call_ext_last Bif D call_ext_only u==1 Bif=u$bif:erts_internal:garbage_collect/1 => i_call_ext_only Bif # # put/2 and erase/1 must be able to do garbage collection, so we must call # them like functions. # call_ext u==2 Bif=u$bif:erlang:put/2 => i_call_ext Bif call_ext_last u==2 Bif=u$bif:erlang:put/2 D => i_call_ext_last Bif D call_ext_only u==2 Bif=u$bif:erlang:put/2 => i_call_ext_only Bif call_ext u==1 Bif=u$bif:erlang:erase/1 => i_call_ext Bif call_ext_last u==1 Bif=u$bif:erlang:erase/1 D => i_call_ext_last Bif D call_ext_only u==1 Bif=u$bif:erlang:erase/1 => i_call_ext_only Bif # # The process_info/1,2 BIF should be called like a function, to force # the emulator to set c_p->current before calling it (a BIF call doesn't # set it). # # In addition, we force the use of a non-tail-recursive call. This will ensure # that c_p->cp points into the function making the call. # call_ext u==1 Bif=u$bif:erlang:process_info/1 => i_call_ext Bif call_ext_last u==1 Bif=u$bif:erlang:process_info/1 D => i_call_ext Bif | deallocate_return D call_ext_only Ar=u==1 Bif=u$bif:erlang:process_info/1 => allocate u Ar | i_call_ext Bif | deallocate_return u call_ext u==2 Bif=u$bif:erlang:process_info/2 => i_call_ext Bif call_ext_last u==2 Bif=u$bif:erlang:process_info/2 D => i_call_ext Bif | deallocate_return D call_ext_only Ar=u==2 Bif=u$bif:erlang:process_info/2 => allocate u Ar | i_call_ext Bif | deallocate_return u # # load_nif/2 also needs to know calling function like process_info # call_ext u==2 Bif=u$bif:erlang:load_nif/2 => i_call_ext Bif call_ext_last u==2 Bif=u$bif:erlang:load_nif/2 D => i_call_ext Bif | deallocate_return D call_ext_only Ar=u==2 Bif=u$bif:erlang:load_nif/2 => allocate u Ar | i_call_ext Bif | deallocate_return u # # apply/2 is an instruction, not a BIF. # call_ext u==2 u$func:erlang:apply/2 => i_apply_fun call_ext_last u==2 u$func:erlang:apply/2 D => i_apply_fun_last D call_ext_only u==2 u$func:erlang:apply/2 => i_apply_fun_only # # The apply/3 BIF is an instruction. # call_ext u==3 u$bif:erlang:apply/3 => i_apply call_ext_last u==3 u$bif:erlang:apply/3 D => i_apply_last D call_ext_only u==3 u$bif:erlang:apply/3 => i_apply_only # # The exit/1 and throw/1 BIFs never execute the instruction following them; # thus there is no need to generate any return instruction. # call_ext_last u==1 Bif=u$bif:erlang:exit/1 D => call_bif Bif call_ext_last u==1 Bif=u$bif:erlang:throw/1 D => call_bif Bif call_ext_only u==1 Bif=u$bif:erlang:exit/1 => call_bif Bif call_ext_only u==1 Bif=u$bif:erlang:throw/1 => call_bif Bif # # The error/1 and error/2 BIFs never execute the instruction following them; # thus there is no need to generate any return instruction. # However, they generate stack backtraces, so if the call instruction # is call_ext_only/2 instruction, we explicitly do an allocate/2 to store # the continuation pointer on the stack. # call_ext_last u==1 Bif=u$bif:erlang:error/1 D => call_bif Bif call_ext_last u==2 Bif=u$bif:erlang:error/2 D => call_bif Bif call_ext_only Ar=u==1 Bif=u$bif:erlang:error/1 => \ allocate u Ar | call_bif Bif call_ext_only Ar=u==2 Bif=u$bif:erlang:error/2 => \ allocate u Ar | call_bif Bif # # The yield/0 BIF is an instruction # call_ext u==0 u$func:erlang:yield/0 => i_yield call_ext_last u==0 u$func:erlang:yield/0 D => i_yield | deallocate_return D call_ext_only u==0 u$func:erlang:yield/0 => i_yield | return # # The hibernate/3 BIF is an instruction. # call_ext u==3 u$func:erlang:hibernate/3 => i_hibernate call_ext_last u==3 u$func:erlang:hibernate/3 D => i_hibernate call_ext_only u==3 u$func:erlang:hibernate/3 => i_hibernate # # If VM probes are not enabled, we want to short-circult calls to # the dt tag BIFs to make them as cheap as possible. # %unless USE_VM_PROBES call_ext Arity u$func:erlang:dt_get_tag/0 => \ move a=am_undefined x=0 call_ext_last Arity u$func:erlang:dt_get_tag/0 D => \ move a=am_undefined x=0 | deallocate D | return call_ext_only Arity u$func:erlang:dt_get_tag/0 => \ move a=am_undefined x=0 | return move Any x==0 | call_ext Arity u$func:erlang:dt_put_tag/1 => \ move a=am_undefined x=0 move Any x==0 | call_ext_last Arity u$func:erlang:dt_put_tag/1 D => \ move a=am_undefined x=0 | deallocate D | return move Any x==0 | call_ext_only Arity u$func:erlang:dt_put_tag/1 => \ move a=am_undefined x=0 | return call_ext Arity u$func:erlang:dt_put_tag/1 => \ move a=am_undefined x=0 call_ext_last Arity u$func:erlang:dt_put_tag/1 D => \ move a=am_undefined x=0 | deallocate D | return call_ext_only Arity u$func:erlang:dt_put_tag/1 => \ move a=am_undefined x=0 | return call_ext Arity u$func:erlang:dt_get_tag_data/0 => \ move a=am_undefined x=0 call_ext_last Arity u$func:erlang:dt_get_tag_data/0 D => \ move a=am_undefined x=0 | deallocate D | return call_ext_only Arity u$func:erlang:dt_get_tag_data/0 => \ move a=am_undefined x=0 | return move Any x==0 | call_ext Arity u$func:erlang:dt_spread_tag/1 => \ move a=am_true x=0 move Any x==0 | call_ext_last Arity u$func:erlang:dt_spread_tag/1 D => \ move a=am_true x=0 | deallocate D | return move Any x==0 | call_ext_only Arity u$func:erlang:dt_spread_tag/1 => \ move a=am_true x=0 | return call_ext Arity u$func:erlang:dt_spread_tag/1 => \ move a=am_true x=0 call_ext_last Arity u$func:erlang:dt_spread_tag/1 D => \ move a=am_true x=0 | deallocate D | return call_ext_only Arity u$func:erlang:dt_spread_tag/1 => \ move a=am_true x=0 | return move Any x==0 | call_ext Arity u$func:erlang:dt_restore_tag/1 => \ move a=am_true x=0 move Any x==0 | call_ext_last Arity u$func:erlang:dt_restore_tag/1 D => \ move a=am_true x=0 | deallocate D | return move Any x==0 | call_ext_only Arity u$func:erlang:dt_restore_tag/1 => \ move a=am_true x=0 | return call_ext Arity u$func:erlang:dt_restore_tag/1 => \ move a=am_true x=0 call_ext_last Arity u$func:erlang:dt_restore_tag/1 D => \ move a=am_true x=0 | deallocate D | return call_ext_only Arity u$func:erlang:dt_restore_tag/1 => \ move a=am_true x=0 | return move Any x==0 | call_ext Arity u$func:erlang:dt_prepend_vm_tag_data/1 => \ move Any x=0 move Any x==0 | call_ext_last Arity u$func:erlang:dt_prepend_vm_tag_data/1 D => \ move Any x=0 | deallocate D | return move Any x==0 | call_ext_only Arity u$func:erlang:dt_prepend_vm_tag_data/1 => \ move Any x=0 | return call_ext Arity u$func:erlang:dt_prepend_vm_tag_data/1 => call_ext_last Arity u$func:erlang:dt_prepend_vm_tag_data/1 D => \ deallocate D | return call_ext_only Arity u$func:erlang:dt_prepend_vm_tag_data/1 => \ return move Any x==0 | call_ext Arity u$func:erlang:dt_append_vm_tag_data/1 => \ move Any x=0 move Any x==0 | call_ext_last Arity u$func:erlang:dt_append_vm_tag_data/1 D => \ move Any x=0 | deallocate D | return move Any x==0 | call_ext_only Arity u$func:erlang:dt_append_vm_tag_data/1 => \ move Any x=0 | return call_ext Arity u$func:erlang:dt_append_vm_tag_data/1 => call_ext_last Arity u$func:erlang:dt_append_vm_tag_data/1 D => \ deallocate D | return call_ext_only Arity u$func:erlang:dt_append_vm_tag_data/1 => \ return # Can happen after one of the transformations above. move Discarded x==0 | move Something x==0 => move Something x=0 %endif call_ext u==0 u$func:os:perf_counter/0 => \ i_perf_counter call_ext_last u==0 u$func:os:perf_counter/0 D => \ i_perf_counter | deallocate_return D call_ext_only u==0 u$func:os:perf_counter/0 => \ i_perf_counter | return # # The general case for BIFs that have no special instructions. # A BIF used in the tail must be followed by a return instruction. # # To make trapping and stack backtraces work correctly, we make sure that # the continuation pointer is always stored on the stack. call_ext u Bif=u$is_bif => call_bif Bif call_ext_last u Bif=u$is_bif D => call_bif Bif | deallocate_return D call_ext_only Ar=u Bif=u$is_bif => \ allocate u Ar | call_bif Bif | deallocate_return u # # Any remaining calls are calls to Erlang functions, not BIFs. # We rename the instructions to internal names. This is necessary, # to avoid an end-less loop, because we want to call a few BIFs # with call instructions. # move S=c x==0 | call_ext Ar=u Func=u$is_not_bif => i_move_call_ext S Func move S=c x==0 | call_ext_last Ar=u Func=u$is_not_bif D => i_move_call_ext_last Func D S move S=c x==0 | call_ext_only Ar=u Func=u$is_not_bif => i_move_call_ext_only Func S call_ext Ar Func => i_call_ext Func call_ext_last Ar Func D => i_call_ext_last Func D call_ext_only Ar Func => i_call_ext_only Func i_apply i_apply_last Q i_apply_only i_apply_fun i_apply_fun_last Q i_apply_fun_only %cold i_hibernate i_perf_counter %hot call_bif e # # Calls to non-building and guard BIFs. # bif0 u$bif:erlang:self/0 Dst=d => self Dst bif0 u$bif:erlang:node/0 Dst=d => node Dst bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => gen_get(Src, Dst) bif2 Jump=j u$bif:erlang:element/2 S1=s S2=xy Dst=d => gen_element(Jump, S1, S2, Dst) bif1 p Bif S1 Dst => bif1_body Bif S1 Dst bif2 p Bif S1 S2 Dst => i_bif2_body Bif S1 S2 Dst bif2 Fail Bif S1 S2 Dst => i_bif2 Fail Bif S1 S2 Dst i_get_hash c I d i_get s d self xy node x %cold node y %hot # Note: 'I' is sufficient because this instruction will only be used # if the arity fits in 24 bits. i_fast_element xy j? I d i_element xy j? s d bif1 f? b s d bif1_body b s d i_bif2 f? b s s d i_bif2_body b s s d # # Internal calls. # move S=cxy x==0 | call Ar P=f => move_call S P move_call/2 move_call cxy f move S x==0 | call_last Ar P=f D => move_call_last S P D move_call_last/3 move_call_last cxy f Q move S=cx x==0 | call_only Ar P=f => move_call_only S P move_call_only/2 move_call_only cx f call Ar Func => i_call Func call_last Ar Func D => i_call_last Func D call_only Ar Func => i_call_only Func i_call f i_call_last f Q i_call_only f i_call_ext e i_call_ext_last e Q i_call_ext_only e i_move_call_ext c e i_move_call_ext_last e Q c i_move_call_ext_only e c # Fun calls. call_fun Arity | deallocate D | return => i_call_fun_last Arity D call_fun Arity => i_call_fun Arity i_call_fun t i_call_fun_last t Q make_fun2 OldIndex=u => gen_make_fun2(OldIndex) %cold i_make_fun W t %hot is_function f? xy is_function Fail=f c => jump Fail func_info M F A => i_func_info u M F A # ================================================================ # New bit syntax matching (R11B). # ================================================================ %warm bs_start_match2 Fail=f ica X Y D => jump Fail bs_start_match2 Fail Bin X Y D => i_bs_start_match2 Bin Fail X Y D i_bs_start_match2 xy f t t x bs_save2 Reg Index => gen_bs_save(Reg, Index) i_bs_save2 x t bs_restore2 Reg Index => gen_bs_restore(Reg, Index) i_bs_restore2 x t # Matching integers bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val i_bs_match_string x f W W # Fetching integers from binaries. bs_get_integer2 Fail=f Ms=x Live=u Sz=sq Unit=u Flags=u Dst=d => \ gen_get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst) i_bs_get_integer_small_imm x W f? t x i_bs_get_integer_imm x W t f? t x i_bs_get_integer f? t t x s x i_bs_get_integer_8 x f? x i_bs_get_integer_16 x f? x %if ARCH_64 i_bs_get_integer_32 x f? x %endif # Fetching binaries from binaries. bs_get_binary2 Fail=f Ms=x Live=u Sz=sq Unit=u Flags=u Dst=d => \ gen_get_binary2(Fail, Ms, Live, Sz, Unit, Flags, Dst) i_bs_get_binary_imm2 f? x t W t x i_bs_get_binary2 f x t? s t x i_bs_get_binary_all2 f? x t t x i_bs_get_binary_all_reuse x f? t # Fetching float from binaries. bs_get_float2 Fail=f Ms=x Live=u Sz=s Unit=u Flags=u Dst=d => \ gen_get_float2(Fail, Ms, Live, Sz, Unit, Flags, Dst) bs_get_float2 Fail=f Ms=x Live=u Sz=q Unit=u Flags=u Dst=d => jump Fail i_bs_get_float2 f? x t s t x # Miscellanous bs_skip_bits2 Fail=f Ms=x Sz=sq Unit=u Flags=u => \ gen_skip_bits2(Fail, Ms, Sz, Unit, Flags) i_bs_skip_bits_imm2 f? x W i_bs_skip_bits2 f? x xy t i_bs_skip_bits_all2 f? x t bs_test_tail2 Fail=f Ms=x Bits=u==0 => bs_test_zero_tail2 Fail Ms bs_test_tail2 Fail=f Ms=x Bits=u => bs_test_tail_imm2 Fail Ms Bits bs_test_zero_tail2 f? x bs_test_tail_imm2 f? x W bs_test_unit F Ms Unit=u==8 => bs_test_unit8 F Ms bs_test_unit f? x t bs_test_unit8 f? x # An y register operand for bs_context_to_binary is rare, # but can happen because of inlining. bs_context_to_binary Y=y | line L | badmatch Y => \ move Y x | bs_context_to_binary x | line L | badmatch x bs_context_to_binary Y=y => move Y x | bs_context_to_binary x bs_context_to_binary x # # Utf8/utf16/utf32 support. (R12B-5) # bs_get_utf8 Fail=f Ms=x u u Dst=d => i_bs_get_utf8 Ms Fail Dst i_bs_get_utf8 x f? x bs_skip_utf8 Fail=f Ms=x u u => i_bs_get_utf8 Ms Fail x bs_get_utf16 Fail=f Ms=x u Flags=u Dst=d => i_bs_get_utf16 Ms Fail Flags Dst bs_skip_utf16 Fail=f Ms=x u Flags=u => i_bs_get_utf16 Ms Fail Flags x i_bs_get_utf16 x f? t x bs_get_utf32 Fail=f Ms=x Live=u Flags=u Dst=d => \ bs_get_integer2 Fail Ms Live i=32 u=1 Flags Dst | \ i_bs_validate_unicode_retract Fail Dst Ms bs_skip_utf32 Fail=f Ms=x Live=u Flags=u => \ bs_get_integer2 Fail Ms Live i=32 u=1 Flags x | \ i_bs_validate_unicode_retract Fail x Ms i_bs_validate_unicode_retract j s S %hot # # Constructing binaries # %warm bs_init2 Fail Sz Words Regs Flags Dst | binary_too_big(Sz) => system_limit Fail bs_init2 Fail Sz=u Words=u==0 Regs Flags Dst => i_bs_init Sz Regs Dst bs_init2 Fail Sz=u Words Regs Flags Dst => \ i_bs_init_heap Sz Words Regs Dst bs_init2 Fail Sz Words=u==0 Regs Flags Dst => \ i_bs_init_fail Sz Fail Regs Dst bs_init2 Fail Sz Words Regs Flags Dst => \ i_bs_init_fail_heap Sz Words Fail Regs Dst i_bs_init_fail xy j? t? x i_bs_init_fail_heap s I j? t? x i_bs_init W t? x i_bs_init_heap W I t? x bs_init_bits Fail Sz=o Words Regs Flags Dst => system_limit Fail bs_init_bits Fail Sz=u Words=u==0 Regs Flags Dst => i_bs_init_bits Sz Regs Dst bs_init_bits Fail Sz=u Words Regs Flags Dst => i_bs_init_bits_heap Sz Words Regs Dst bs_init_bits Fail Sz Words=u==0 Regs Flags Dst => \ i_bs_init_bits_fail Sz Fail Regs Dst bs_init_bits Fail Sz Words Regs Flags Dst => \ i_bs_init_bits_fail_heap Sz Words Fail Regs Dst i_bs_init_bits_fail xy j? t? x i_bs_init_bits_fail_heap s I j? t? x i_bs_init_bits W t? x i_bs_init_bits_heap W I t? x bs_add Fail S1=i==0 S2 Unit=u==1 D => move S2 D bs_add j? s s t? x bs_append Fail Size Extra Live Unit Bin Flags Dst => \ move Bin x | i_bs_append Fail Extra Live Unit Size Dst bs_private_append Fail Size Unit Bin Flags Dst => \ i_bs_private_append Fail Unit Size Bin Dst bs_init_writable i_bs_append j? I t? t s x i_bs_private_append j? t s S x # # Storing integers into binaries. # bs_put_integer Fail=j Sz=sq Unit=u Flags=u Src=s => \ gen_put_integer(Fail, Sz, Unit, Flags, Src) i_new_bs_put_integer j? s t s i_new_bs_put_integer_imm j? W t s # # Utf8/utf16/utf32 support. (R12B-5) # bs_utf8_size j Src=s Dst=d => i_bs_utf8_size Src Dst i_bs_utf8_size s x bs_utf16_size j Src=s Dst=d => i_bs_utf16_size Src Dst i_bs_utf16_size s x bs_put_utf8 Fail u Src=s => i_bs_put_utf8 Fail Src i_bs_put_utf8 j? s bs_put_utf16 j? t s bs_put_utf32 Fail=j Flags=u Src=s => \ i_bs_validate_unicode Fail Src | bs_put_integer Fail i=32 u=1 Flags Src i_bs_validate_unicode j? s # # Storing floats into binaries. # bs_put_float Fail Sz=q Unit Flags Val => badarg Fail bs_put_float Fail=j Sz=s Unit=u Flags=u Src=s => \ gen_put_float(Fail, Sz, Unit, Flags, Src) i_new_bs_put_float j? s t s i_new_bs_put_float_imm j? W t s # # Storing binaries into binaries. # bs_put_binary Fail=j Sz=s Unit=u Flags=u Src=s => \ gen_put_binary(Fail, Sz, Unit, Flags, Src) i_new_bs_put_binary j? s t s i_new_bs_put_binary_imm j? W s i_new_bs_put_binary_all j? s t # # Warning: The i_bs_put_string and i_new_bs_put_string instructions # are specially treated in the loader. # Don't change the instruction format unless you change the loader too. # bs_put_string W W # # New floating point instructions (R8). # fadd p FR1 FR2 FR3 => i_fadd FR1 FR2 FR3 fsub p FR1 FR2 FR3 => i_fsub FR1 FR2 FR3 fmul p FR1 FR2 FR3 => i_fmul FR1 FR2 FR3 fdiv p FR1 FR2 FR3 => i_fdiv FR1 FR2 FR3 fnegate p FR1 FR2 => i_fnegate FR1 FR2 fconv Arg=iqan Dst=l => move Arg x | fconv x Dst fmove Arg=l Dst=d => fstore Arg Dst fmove Arg=dq Dst=l => fload Arg Dst fstore l d fload Sq l fconv S l i_fadd l l l i_fsub l l l i_fmul l l l i_fdiv l l l i_fnegate l l fclearerror | no_fpe_signals() => fcheckerror p | no_fpe_signals() => %unless NO_FPE_SIGNALS fcheckerror p => i_fcheckerror i_fcheckerror fclearerror %endif %hot # # New apply instructions in R10B. # apply t apply_last t Q # # Handle compatibility with OTP 17 here. # i_put_map_assoc/4 # We KNOW that in OTP 20 (actually OTP 18 and higher), a put_map_assoc instruction # is always preceded by an is_map test. That means that put_map_assoc can never # fail and does not need any failure label. put_map_assoc Fail Map Dst Live Size Rest=* | compiled_with_otp_20_or_higher() => \ i_put_map_assoc Map Dst Live Size Rest # Translate the put_map_assoc instruction if the module was compiled by a compiler # before 20. This is only necessary if the OTP 17 compiler was used, but we # have no safe and relatively easy way to know whether OTP 18/19 was used. put_map_assoc Fail=p Map Dst Live Size Rest=* => \ ensure_map Map | i_put_map_assoc Map Dst Live Size Rest put_map_assoc Fail=f Map Dst Live Size Rest=* => \ is_map Fail Map | i_put_map_assoc Map Dst Live Size Rest ensure_map Lit=q | literal_is_map(Lit) => ensure_map Src=cqy => move Src x | ensure_map x %cold ensure_map x %hot # # Map instructions. First introduced in R17. # sorted_put_map_assoc/4 i_put_map_assoc Map Dst Live Size Rest=* | map_key_sort(Size, Rest) => \ sorted_put_map_assoc Map Dst Live Size Rest sorted_put_map_exact/5 put_map_exact F Map Dst Live Size Rest=* | map_key_sort(Size, Rest) => \ sorted_put_map_exact F Map Dst Live Size Rest sorted_put_map_assoc Map Dst Live Size Rest=* | is_empty_map(Map) => \ new_map Dst Live Size Rest sorted_put_map_assoc Src=s Dst Live Size Rest=* => \ update_map_assoc Src Dst Live Size Rest sorted_put_map_assoc Src Dst Live Size Rest=* => \ move Src x | update_map_assoc x Dst Live Size Rest sorted_put_map_exact F Src=s Dst Live Size Rest=* => \ update_map_exact F Src Dst Live Size Rest sorted_put_map_exact F Src Dst Live Size Rest=* => \ move Src x | update_map_exact F x Dst Live Size Rest new_map Dst Live Size Rest=* | is_small_map_literal_keys(Size, Rest) => \ gen_new_small_map_lit(Dst, Live, Size, Rest) new_map d t I i_new_small_map_lit d t q update_map_assoc s d t I update_map_exact j? s d t I is_map Fail Lit=q | literal_is_map(Lit) => is_map Fail cq => jump Fail is_map f? xy ## Transform has_map_fields #{ K1 := _, K2 := _ } to has_map_elements has_map_fields Fail Src Size Rest=* => \ gen_has_map_fields(Fail, Src, Size, Rest) ## Transform get_map_elements(s) #{ K1 := V1, K2 := V2 } get_map_elements Fail Src=xy Size=u==2 Rest=* => \ gen_get_map_element(Fail, Src, Size, Rest) get_map_elements Fail Src Size Rest=* | map_key_sort(Size, Rest) => \ gen_get_map_elements(Fail, Src, Size, Rest) i_get_map_elements f? s I i_get_map_element Fail Src=xy Key=y Dst => \ move Key x | i_get_map_element Fail Src x Dst i_get_map_element_hash f? xy c I xy i_get_map_element f? xy x xy # # Convert the plus operations to a generic plus instruction. # gen_plus/5 gen_minus/5 gc_bif1 Fail Live u$bif:erlang:splus/1 Src Dst => \ gen_plus Fail Live Src i Dst gc_bif2 Fail Live u$bif:erlang:splus/2 S1 S2 Dst => \ gen_plus Fail Live S1 S2 Dst gc_bif1 Fail Live u$bif:erlang:sminus/1 Src Dst => \ gen_minus Fail Live i Src Dst gc_bif2 Fail Live u$bif:erlang:sminus/2 S1 S2 Dst => \ gen_minus Fail Live S1 S2 Dst # # Optimize addition and subtraction of small literals using # the i_increment/4 instruction (in bodies, not in guards). # gen_plus p Live Int=i Reg=d Dst => \ gen_increment(Reg, Int, Live, Dst) gen_plus p Live Reg=d Int=i Dst => \ gen_increment(Reg, Int, Live, Dst) gen_minus p Live Reg=d Int=i Dst | negation_is_small(Int) => \ gen_increment_from_minus(Reg, Int, Live, Dst) # # GCing arithmetic instructions. # gen_plus Fail Live S1 S2 Dst => i_plus S1 S2 Fail Live Dst gen_minus Fail Live S1 S2 Dst => i_minus S1 S2 Fail Live Dst gc_bif2 Fail Live u$bif:erlang:stimes/2 S1 S2 Dst => \ i_times Fail Live S1 S2 Dst gc_bif2 Fail Live u$bif:erlang:div/2 S1 S2 Dst => \ i_m_div Fail Live S1 S2 Dst gc_bif2 Fail Live u$bif:erlang:intdiv/2 S1 S2 Dst => \ i_int_div Fail Live S1 S2 Dst gc_bif2 Fail Live u$bif:erlang:rem/2 S1 S2 Dst => \ i_rem S1 S2 Fail Live Dst gc_bif2 Fail Live u$bif:erlang:bsl/2 S1 S2 Dst => \ i_bsl S1 S2 Fail Live Dst gc_bif2 Fail Live u$bif:erlang:bsr/2 S1 S2 Dst => \ i_bsr S1 S2 Fail Live Dst gc_bif2 Fail Live u$bif:erlang:band/2 S1 S2 Dst => \ i_band S1 S2 Fail Live Dst gc_bif2 Fail Live u$bif:erlang:bor/2 S1 S2 Dst => \ i_bor Fail Live S1 S2 Dst gc_bif2 Fail Live u$bif:erlang:bxor/2 S1 S2 Dst => \ i_bxor Fail Live S1 S2 Dst gc_bif1 Fail I u$bif:erlang:bnot/1 Src Dst=d => i_int_bnot Fail Src I Dst i_increment rxy W t d i_plus x xy j? t d i_plus s s j? t d i_minus x x j? t d i_minus s s j? t d i_times j? t s s d i_m_div j? t s s d i_int_div j? t s s d i_rem x x j? t d i_rem s s j? t d i_bsl s s j? t d i_bsr s s j? t d i_band x c j? t d i_band s s j? t d i_bor j? I s s d i_bxor j? I s s d i_int_bnot Fail Src=c Live Dst => move Src x | i_int_bnot Fail x Live Dst i_int_bnot j? S t d # # Old guard BIFs that creates heap fragments are no longer allowed. # bif1 Fail u$bif:erlang:length/1 s d => too_old_compiler bif1 Fail u$bif:erlang:size/1 s d => too_old_compiler bif1 Fail u$bif:erlang:abs/1 s d => too_old_compiler bif1 Fail u$bif:erlang:float/1 s d => too_old_compiler bif1 Fail u$bif:erlang:round/1 s d => too_old_compiler bif1 Fail u$bif:erlang:trunc/1 s d => too_old_compiler # # Guard BIFs. # gc_bif1 Fail I Bif Src Dst => \ gen_guard_bif1(Fail, I, Bif, Src, Dst) gc_bif2 Fail I Bif S1 S2 Dst => \ gen_guard_bif2(Fail, I, Bif, S1, S2, Dst) gc_bif3 Fail I Bif S1 S2 S3 Dst => \ gen_guard_bif3(Fail, I, Bif, S1, S2, S3, Dst) i_gc_bif1 j? W s t? d i_gc_bif2 j? W t? s s d ii_gc_bif3/7 # A specific instruction can only have 6 operands, so we must # pass one of the arguments in an x register. ii_gc_bif3 Fail Bif Live S1 S2 S3 Dst => \ move S1 x | i_gc_bif3 Fail Bif Live S2 S3 Dst i_gc_bif3 j? W t? s s d # # The following instruction is specially handled in beam_load.c # to produce a user-friendly message if an unsupported guard BIF is # encountered. # unsupported_guard_bif/3 unsupported_guard_bif A B C | never() => # # R13B03 # on_load # # R14A. # # Modified in OTP 21 because it turns out that we don't need the # label after all. # recv_mark f => i_recv_mark i_recv_mark recv_set Fail | label Lbl | loop_rec Lf Reg => \ i_recv_set | label Lbl | loop_rec Lf Reg i_recv_set # # OTP 21. # build_stacktrace