diff options
Diffstat (limited to 'erts/emulator/beam/jit/x86/ops.tab')
-rw-r--r-- | erts/emulator/beam/jit/x86/ops.tab | 1349 |
1 files changed, 1349 insertions, 0 deletions
diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab new file mode 100644 index 0000000000..e33cc110a5 --- /dev/null +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -0,0 +1,1349 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2019. 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% +# + +# +# Types that should never be used in specific operations. +# + +FORBIDDEN_TYPES=hQ + +# +# 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 +# An unaligned label. The address of an unaligned label must never be saved +# on the stack or used in a context where it can be confused with an Erlang term. + +label L + +# An label aligned to a certain boundary. This is used in two cases: +# +# * When the label points to the start of a function, as the ErtsCodeInfo +# struct must be word-aligned. +# * When the address is stored on the stack or otherwise needs to be properly +# tagged as a continuation pointer. +aligned_label L t + +i_func_info I a a I +int_code_end + +i_generic_breakpoint +i_debug_breakpoint +i_return_time_trace +i_return_to_trace +trace_jump W +i_yield +%hot + +return + +# +# A tail call will not refer to the current function on error unless it's a +# BIF, so we can omit the line instruction for non-BIFs. +# + +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_last Ar Func D => \ + move S X0 | call_last Ar Func D +move S X0=x==0 | line Loc | call_only Ar Func => \ + move S X0 | call_only Ar Func + +# The line number in int_func_start/5 can be NIL. +func_line n => empty_func_line + +empty_func_line +func_line I + +line n => +line I + +allocate t t? +allocate_heap t I t? + +deallocate t + +init y + +trim N Remaining => i_trim N + +i_trim t + +test_heap I t? + +# Translate instructions generated by a compiler before OTP 24. +allocate_zero Ns Live => allocate_heap_zero Ns u Live +allocate_heap_zero Ns Nh Live => allocate_heap_zero(Ns, Nh, Live) + +init_yregs I * + +# Selecting values. + +# The size of the dispatch code for a jump table is about 40 +# bytes. Therefore we shouldn't use a jump table if there are too few +# values. + +select_val S Fail=fn Size=u Rest=* | use_jump_tab(Size, Rest, 6) => \ + jump_tab(S, Fail, Size, Rest) +is_integer Fail=f S | select_val S=s Fail=fn Size=u Rest=* | use_jump_tab(Size, Rest, 6) => \ + jump_tab(S, Fail, Size, Rest) + +is_integer TypeFail=f S | select_val S=s Fail=fn Size=u Rest=* | \ + mixed_types(Size, Rest) => \ + split_values(S, TypeFail, Fail, Size, Rest) + +select_val S Fail=fn Size=u Rest=* | mixed_types(Size, Rest) => \ + split_values(S, Fail, Fail, Size, Rest) + +is_integer Fail=f S | select_val S=d Fail=fn Size=u Rest=* | \ + fixed_size_values(Size, Rest) => select_val(S, Fail, Size, Rest) + +is_atom Fail=f S | select_val S=d Fail=fn Size=u Rest=* | \ + fixed_size_values(Size, Rest) => select_val(S, Fail, Size, Rest) + +select_val S Fail=fn Size=u Rest=* | floats_or_bignums(Size, Rest) => \ + select_literals(S, Fail, Size, Rest) + +select_val S Fail=fn Size=u Rest=* | fixed_size_values(Size, Rest) => \ + select_val(S, Fail, Size, Rest) + +is_tuple Fail=f S | select_tuple_arity S=d Fail=f Size=u Rest=* => \ + select_tuple_arity(S, Fail, Size, Rest) + +select_tuple_arity S=d Fail=f Size=u Rest=* => \ + select_tuple_arity(S, Fail, Size, Rest) + +i_select_val_bins s fn I * + +i_select_val_lins s fn I * + +i_select_tuple_arity S f? I * + +i_jump_on_val s fn I I * + +is_number f? s + +jump f + + +# +# List matching instructions. The combination of test for a nonempty list followed +# by get_{list/hd/tl} are common, so we will optimize that. +# +is_nonempty_list Fail nqia => jump Fail +is_nonempty_list Fail Src | get_list Src Hd Tl => is_nonempty_list_get_list Fail Src Hd Tl +is_nonempty_list Fail Src | get_hd Src Hd => is_nonempty_list_get_hd Fail Src Hd +is_nonempty_list Fail Src | get_tl Src Tl => is_nonempty_list_get_tl Fail Src Tl + +is_nonempty_list f? S + +get_list S d d +get_hd S d +get_tl S d + +is_nonempty_list_get_list f S d d +is_nonempty_list_get_hd f S d +is_nonempty_list_get_tl f S d + +# Old-style catch. +catch y f +catch_end y + +# Try/catch. +try Y F => catch Y F + +try_case y +try_end y + +try_case_end s + +# Destructive set tuple element + +set_tuple_element s S P + +# +# Get tuple element. Since this instruction is frequently used, we will try +# to only fetch the pointer to the tuple once for a sequence of BEAM instructions +# that fetch multiple elements from the same tuple. +# + +current_tuple/1 +current_tuple/2 + +is_tuple Fail=f Src | test_arity Fail Src Arity => \ + i_is_tuple_of_arity Fail Src Arity | current_tuple Src + +test_arity Fail Src Arity => i_test_arity Fail Src Arity | current_tuple Src + +is_tuple NotTupleFail Tuple | is_tagged_tuple WrongRecordFail Tuple Arity Atom => \ + i_is_tagged_tuple_ff NotTupleFail WrongRecordFail Tuple Arity Atom | current_tuple Tuple + +is_tagged_tuple Fail Tuple Arity Atom => \ + i_is_tagged_tuple Fail Tuple Arity Atom | current_tuple Tuple + +is_tuple Fail=f Src => i_is_tuple Fail Src | current_tuple Src + +i_is_tuple_of_arity f? s A +i_test_arity f? s A + +i_is_tagged_tuple f? s A a +i_is_tagged_tuple_ff f? f? s A a + +i_is_tuple f? s + +# Generate instruction sequence for fetching the tuple element and remember +# that we have a current tuple pointer. + +get_tuple_element Tuple Pos Dst => \ + load_tuple_ptr Tuple | i_get_tuple_element Tuple Pos Dst | current_tuple Tuple +current_tuple Tuple | get_tuple_element Tuple Pos Dst => \ + i_get_tuple_element Tuple Pos Dst | current_tuple Tuple + +# Drop the current_tuple instruction if the tuple is overwritten. +i_get_tuple_element Tuple Pos Tuple | current_tuple Tuple => i_get_tuple_element Tuple Pos Tuple + +# This is a current_tuple instruction instruction not followed by get_tuple_element. +# Invalidate the current tuple pointer. + +current_tuple Tuple => + +load_tuple_ptr s + +# If both positions and destinations are in consecutive memory, fetch and store +# two words at once. +i_get_tuple_element Tuple Pos1 Dst1 | current_tuple Tuple | \ + get_tuple_element Tuple Pos2 Dst2 | consecutive_words(Pos1, Dst1, Pos2, Dst2) => \ + get_two_tuple_elements Tuple Pos1 Dst1 Dst2 | current_tuple Tuple Dst2 +i_get_tuple_element Tuple Pos1 Dst1 | current_tuple Tuple | \ + get_tuple_element Tuple Pos2 Dst2 | consecutive_words(Pos1, Dst2, Pos2, Dst1) => \ + get_two_tuple_elements Tuple Pos1 Dst1 Dst2 | current_tuple Tuple Dst2 + +# Drop the current_tuple instruction if the tuple is overwritten. +current_tuple Tuple Tuple => +current_tuple Tuple Dst => current_tuple Tuple + +# The first operand will only be used in the debug-compiled runtime +# system to verify that the register holding the tuple pointer agrees +# with the source tuple operand. +i_get_tuple_element s P S +get_two_tuple_elements s P S S + +# +# Expection rasing instructions. Infrequently executed. +# + +%cold +case_end s + +badmatch s + +if_end + +raise s s + +# Workaround the limitation that generators must always return at least one instruction. +delete_me/0 +delete_me => + +system_limit/1 +system_limit p => system_limit_body +system_limit Fail=f => jump Fail + +system_limit_body + +%hot + +# +# Optimize moves of consecutive memory addresses. +# + +move Src=c Dst => i_move Src Dst + +move Src SrcDst | move SrcDst Dst => i_move Src SrcDst | move SrcDst Dst + +# Try to move two words at once. Always arrange the source operands in +# consecutive order; the destination operands may be in consecutive or +# reverse consecutive order. + +move S1=d D1=d | move S2=d D2=d | consecutive_words(S1, D1, S2, D2) => move_two_words S1 D1 S2 D2 +move S1=d D1=d | move S2=d D2=d | consecutive_words(S1, D2, S2, D1) => move_two_words S1 D1 S2 D2 +move S1=d D1=d | move S2=d D2=d | consecutive_words(S2, D1, S1, D2) => move_two_words S2 D2 S1 D1 +move S1=d D1=d | move S2=d D2=d | consecutive_words(S2, D2, S1, D1) => move_two_words S2 D2 S1 D1 + +move Src Dst => i_move Src Dst + +# +# Move instructions. +# + +swap d d +i_move s d +move_two_words s d s d + +# +# Receive operations. We conservatively align all labels before any +# of the receive instructions. +# +# As the labels may be stored in the process structure, we must align them to +# the nearest 4-byte boundary to ensure they're properly tagged as continuation +# pointers. +# + +label L | loop_rec Fail Reg => \ + aligned_label L u=4 | loop_rec Fail Reg +label L | wait_timeout Fail Src => \ + aligned_label L u=4 | wait_timeout Fail Src +label L | wait Fail => \ + aligned_label L u=4 | wait Fail +label L | timeout => \ + aligned_label L u=4 | timeout + +loop_rec Fail x==0 | smp_mark_target_label(Fail) => i_loop_rec Fail + +aligned_label L A | wait_timeout Fail Src | smp_already_locked(L) => \ + aligned_label L A | wait_timeout_locked Src Fail +wait_timeout Fail Src => wait_timeout_unlocked Src Fail + +aligned_label L A | wait Fail | smp_already_locked(L) => \ + aligned_label L A | wait_locked Fail +wait Fail => wait_unlocked Fail + +aligned_label L A | timeout | smp_already_locked(L) => \ + aligned_label L A | 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 s f +wait_timeout_locked s f + +send + +# +# Optimized comparisons with one immediate/literal operand. +# + +is_eq_exact Lbl S S => +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 => is_eq_exact_literal(Lbl, R, C) + +is_ne_exact Lbl S S => jump Lbl +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? s c + +i_is_eq_exact_literal/4 +i_is_eq_exact_literal f? s c I + +i_is_ne_exact_immed f? s c + +i_is_ne_exact_literal f? s c + +is_eq_exact f? s s + +is_ne_exact f? s s + +is_lt f? s s +is_ge f? s s + +is_eq Fail=f Const=c Reg=xy => is_eq Fail Reg Const +is_eq f? s s + +is_ne Fail=f Const=c Reg=xy => is_ne Fail Reg Const +is_ne f? s s + +# +# Putting tuples. +# +# Code compiled with OTP 22 and later uses put_tuple2 to +# to construct a tuple. +# +# Code compiled before OTP 22 uses put_tuple + one put instruction +# per element. Translate to put_tuple2. +# + +i_put_tuple/2 +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 Dst Arity Puts=* => put_tuple2 Dst Arity Puts + +put_tuple2 S A * + +# +# Putting lists. +# + +store_cons/2 +put_list Hd Tl Dst => put_cons Hd Tl | store_cons u=1 Dst +store_cons Len Dst | put_list Hd Dst Dst | distinct(Dst, Hd) => combine_conses(Len, Dst, Hd) +store_cons Len Dst | put_list Hd Dst OtherDst => store_cons Len Dst | append_cons u Hd | store_cons u=1 OtherDst + +put_cons s s +append_cons I s +store_cons I d + +# +# Some more only used by the emulator +# + +%cold +normal_exit +continue_exit +call_bif W +call_bif_mfa a a I +call_nif W W W +call_error_handler +error_action_code +return_trace +%hot + +# +# Type tests. Note that the operands for most type tests are `s` to +# ensure that literal operands will work. The BEAM compiler starting +# from OTP 22 will never emit type tests with literal operands even if +# all optimizations are turned off, but loading unoptimized code from +# older releases and code generated by alternative code generators. +# + +is_integer f? s +is_list f? s +is_atom f? s +is_float f? s + +is_nil Fail=f n => +is_nil Fail=f qia => jump Fail +is_nil f? S + +# XXX Deprecated. +is_bitstr Fail Term => is_bitstring Fail Term + +is_binary f? s +is_bitstring f? s + +is_reference f? s +is_pid f? s +is_port f? s + +is_boolean f? s + +is_function2 f? s s + +################################################################# +# External function and bif calls. +################################################################# + +# Expands into call_light_bif/2 +call_light_bif/1 + +# +# The load_nif/2 BIF is an instruction. +# + +call_ext u==2 u$func:erlang:load_nif/2 => \ + i_load_nif +call_ext_last u==2 u$func:erlang:load_nif/2 D => \ + i_load_nif | deallocate D | return +call_ext_only u==2 u$func:erlang:load_nif/2 => \ + i_load_nif | return + +%cold +i_load_nif +%hot + +# +# 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$func:erlang:apply/3 => i_apply +call_ext_last u==3 u$func:erlang:apply/3 D => i_apply_last D +call_ext_only u==3 u$func:erlang:apply/3 => i_apply_only + +# +# 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 D | return +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 + +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 D | return +call_ext_only u==0 u$func:os:perf_counter/0 => \ + i_perf_counter | return + +# +# BIFs like process_info/1,2 require up-to-date information about the current +# emulator state, which the ordinary call_light_bif instruction doesn't save. +# + +call_ext u Bif=u$is_bif | is_heavy_bif(Bif) => \ + i_call_ext Bif +call_ext_last u Bif=u$is_bif D | is_heavy_bif(Bif) => \ + i_call_ext Bif | deallocate D | return +call_ext_only Ar=u Bif=u$is_bif | is_heavy_bif(Bif) => \ + allocate u Ar | i_call_ext Bif | deallocate u | return + +# +# The general case for BIFs that have no special requirements. +# + +call_ext u Bif=u$is_bif => \ + call_light_bif Bif +call_ext_last u Bif=u$is_bif D => \ + call_light_bif Bif | deallocate D | return +call_ext_only Ar=u Bif=u$is_bif => \ + allocate u Ar | call_light_bif Bif | deallocate u | return + +# +# 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. +# + +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_validate t + +i_apply +i_apply_last t +i_apply_only + +i_apply_fun +i_apply_fun_last t +i_apply_fun_only + +call_light_bif Bif => call_light_bif Bif Bif +call_light_bif b e + +%cold + +i_hibernate +i_perf_counter + +%hot + +# +# 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=f Bif=u$bif:erlang:hd/1 Src=n Dst => \ + jump Fail +bif1 Fail=f Bif=u$bif:erlang:hd/1 Src Dst => \ + is_nonempty_list Fail Src | get_hd Src Dst +bif1 Fail Bif=u$bif:erlang:hd/1 Src Dst => \ + bif_hd Fail Src Dst + +bif_hd j? s d + +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=n Dst => \ + jump Fail +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src Dst => \ + is_nonempty_list Fail Src | get_tl Src Dst + +bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => get(Src, Dst) + +bif2 Fail u$bif:erlang:element/2 S1=ixy S2 Dst => bif_element Fail S1 S2 Dst + +bif_element j? s s d + +bif1 Fail Bif S1 Dst | never_fails(Bif) => nofail_bif1 S1 Bif Dst +bif2 Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif Dst + +bif1 Fail Bif S1 Dst => i_bif1 S1 Fail Bif Dst +bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst + +nofail_bif2 S1=d S2=ian Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact_immed S1 S2 Dst +nofail_bif2 S1=d S2=ian Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact_immed S1 S2 Dst + +i_get_hash c I d +i_get s d + +self d + +node d + +nofail_bif1 s b d +nofail_bif2 s s b d + +i_bif1 s j? b d +i_bif2 s s j? b d +i_bif3 s s s j? b d + +bif_is_eq_exact_immed S c d +bif_is_ne_exact_immed S c d + +# +# Internal calls. +# + +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 t +i_call_only f + +i_call_ext e +i_call_ext_last e t +i_call_ext_only e + +# 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 t + +# +# A fun with an empty environment can be converted to a literal. +# As a further optimization, the we try to move the fun to its +# final destination directly. + +make_fun2 OldIndex=u => make_fun2(OldIndex) +make_fun3 OldIndex=u Dst=d NumFree=u Env=* => make_fun3(OldIndex, Dst, NumFree, Env) + +%cold + +i_make_fun3 F S t * + +# Psuedo-instruction for signalling lambda load errors. Never actually runs. +i_lambda_error t + +%hot + +is_function f? S +is_function Fail=f c => jump Fail + +# The start and end of a function. +int_func_start/5 +int_func_end/0 + +func_prologue/2 + +int_func_start Func_Label Func_Line M F A | \ + label Entry_Label | line Entry_Line => \ + int_func_start Func_Label Func_Line M F A | \ + func_prologue Entry_Label Entry_Line + +int_func_start Func_Label Func_Line M F A | \ + label Entry_Label => \ + int_func_start Func_Label Func_Line M F A | \ + func_prologue Entry_Label n + +int_func_start Func_Label Func_Line M F A | \ + func_prologue Entry_Label Entry_Line | \ + is_mfa_bif(M, F, A) => \ + aligned_label Func_Label u=8 | \ + func_line Func_Line | \ + i_func_info Func_Label M F A | \ + aligned_label Entry_Label u=8 | \ + line Entry_Line | \ + i_breakpoint_trampoline | \ + call_bif_mfa M F A + +int_func_start Func_Label Func_Line M F A | \ + func_prologue Entry_Label Entry_Line => \ + aligned_label Func_Label u=8 | \ + func_line Func_Line | \ + i_func_info Func_Label M F A | \ + aligned_label Entry_Label u=8 | \ + line Entry_Line | \ + i_breakpoint_trampoline | \ + i_test_yield + + +int_func_end | needs_nif_padding() => i_nif_padding +int_func_end => + +# Handles yielding on function ingress (rather than on each call). +i_test_yield + +# Ensures that the prior function is large enough to allow NIF patching. +i_nif_padding + +# Handles tracing, early NIF calls, and so on. +i_breakpoint_trampoline + +# ================================================================ +# Bit syntax matching obsoleted in OTP 22. +# ================================================================ + +%cold +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 S f t t d + +bs_save2 Reg Index => bs_save(Reg, Index) +i_bs_save2 x t + +bs_restore2 Reg Index => bs_restore(Reg, Index) +i_bs_restore2 S t + +bs_context_to_binary S +%warm + +# ================================================================ +# New bit syntax matching (R11B). +# ================================================================ + +%warm + +# Matching integers +bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val + +i_bs_match_string S f W W + +# Fetching integers from binaries. +bs_get_integer2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d => \ + get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst) + +i_bs_get_integer S f? t t s d + +i_bs_get_integer_8 S t f? d +i_bs_get_integer_16 S t f? d +i_bs_get_integer_32 S t f? d +i_bs_get_integer_64 S t f? t d + +# Fetching binaries from binaries. +bs_get_binary2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d => \ + get_binary2(Fail, Ms, Live, Sz, Unit, Flags, Dst) + +i_bs_get_binary2 S f t? s t d +i_bs_get_binary_all2 S f? t t d + +# Fetching float from binaries. +bs_get_float2 Fail=f Ms=xy Live=u Sz=s Unit=u Flags=u Dst=d => \ + 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 S f? t s t d + +# Miscellanous + +bs_skip_bits2 Fail=f Ms=xy Sz=sq Unit=u Flags=u => \ + skip_bits2(Fail, Ms, Sz, Unit, Flags) + +i_bs_skip_bits_imm2 f? S W +i_bs_skip_bits2 S S f? t + +bs_test_tail2 f? S t + +bs_test_unit f? S t + +# Gets a bitstring from the tail of a context. +bs_get_tail S d t + +# New bs_start_match variant for contexts with external position storage. +# +# bs_get/set_position is used to save positions into registers instead of +# "slots" in the context itself, which lets us continue matching even after +# we've passed it off to another function. + +bs_start_match4 a==am_no_fail Live=u Src=xy Ctx=d => \ + bs_start_match3 p Src Live Ctx +bs_start_match4 Fail=f Live=u Src=xy Ctx=d => \ + bs_start_match3 Fail Src Live Ctx + +%if ARCH_64 + +# This instruction nops on 64-bit platforms +bs_start_match4 a==am_resume Live Same Same => +bs_start_match4 a==am_resume Live Ctx Dst => move Ctx Dst + +%else + +bs_start_match4 a==am_resume Live Ctx Dst => \ + bs_start_match4 a=am_no_fail Live Ctx Dst + +%endif + +bs_start_match3 Fail=j ica Live Dst => jump Fail +bs_start_match3 Fail Bin Live Dst => i_bs_start_match3 Bin Live Fail Dst + +i_bs_start_match3 S t j d + +# Match context position instructions. 64-bit assumes that all positions can +# fit into an unsigned small. + +%if ARCH_64 + bs_get_position Src Dst Live => i_bs_get_position Src Dst + i_bs_get_position S S + bs_set_position S S +%else + bs_get_position S d t? + bs_set_position S S +%endif + +# +# Utf8/utf16/utf32 support. (R12B-5) +# +bs_get_utf8 Fail=f Ms=xy u u Dst=d => i_bs_get_utf8 Ms Fail Dst +i_bs_get_utf8 S f? d + +bs_skip_utf8 Fail=f Ms=xy u u => i_bs_skip_utf8 Ms Fail +i_bs_skip_utf8 S f? + +bs_get_utf16 Fail=f Ms=xy u Flags=u Dst=d => i_bs_get_utf16 Ms Fail Flags Dst +bs_skip_utf16 Fail=f Ms=xy u Flags=u => i_bs_skip_utf16 Ms Fail Flags + +i_bs_get_utf16 S f? t d +i_bs_skip_utf16 S f? t + +bs_get_utf32 Fail=f Ms=xy 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=xy 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 S j? t? S + +i_bs_init_fail_heap s I j? t? S + +i_bs_init W t? S + +i_bs_init_heap W I t? S + + +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 S j? t? S + +i_bs_init_bits_fail_heap s I j? t? S + +i_bs_init_bits W t? S +i_bs_init_bits_heap W I t? S + +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 => \ + i_bs_append Fail Extra Live Unit Size Bin Dst + +bs_private_append Fail Size Unit Bin Flags Dst => \ + i_bs_private_append Fail Unit Size Bin Dst + +i_bs_private_append Fail Unit Size Bin Dst=y => \ + i_bs_private_append Fail Unit Size Bin x | move x Dst + +bs_init_writable + +i_bs_append j? I t? t s s S +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 => \ + put_integer(Fail, Sz, Unit, Flags, Src) + +i_new_bs_put_integer j? S t s +i_new_bs_put_integer_imm s j? W t + +# +# Utf8/utf16/utf32 support. (R12B-5) +# + +bs_utf8_size j Src Dst=d => i_bs_utf8_size Src Dst +bs_utf16_size j Src Dst=d => i_bs_utf16_size Src Dst + +bs_put_utf8 Fail u Src => i_bs_put_utf8 Fail Src + +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_utf8_size s x +i_bs_utf16_size s x + +i_bs_put_utf8 j? s +bs_put_utf16 j? t s + +i_bs_validate_unicode j? s + +# +# Storing floats into binaries. +# + +# Will fail. No need to keep the instruction, because bs_add or +# bs_init* would already have raised an exception. +bs_put_float Fail Sz=q Unit Flags Val => + +bs_put_float Fail=j Sz=s Unit=u Flags=u Src=s => \ + 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 => \ + 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 s j? 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 + +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 => +fcheckerror p => + +%hot + +# +# New apply instructions in R10B. +# + +apply t +apply_last t t + +# +# 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) => + +%cold +ensure_map s +%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_exact Fail Src Dst Live Size Rest=* => \ + update_map_exact Src Fail Dst Live Size Rest + +new_map Dst Live Size Rest=* | is_small_map_literal_keys(Size, Rest) => \ + new_small_map_lit(Dst, Live, Size, Rest) + +new_map d t I * +i_new_small_map_lit d t q I * +update_map_assoc s d t I * +update_map_exact s j? d t I * + +is_map f? s + +## Transform has_map_fields #{ K1 := _, K2 := _ } to has_map_elements + +has_map_fields Fail Src Size Rest=* => \ + has_map_fields(Fail, Src, Size, Rest) + +## Transform get_map_elements(s) #{ K1 := V1, K2 := V2 } + +get_map_elements Fail Src Size=u==2 Rest=* => \ + get_map_element(Fail, Src, Size, Rest) +get_map_elements Fail Src Size Rest=* | map_key_sort(Size, Rest) => \ + get_map_elements(Fail, Src, Size, Rest) + +i_get_map_elements f? s I * + +i_get_map_element_hash Fail Src=c Key Hash Dst => \ + move Src x | i_get_map_element_hash Fail x Key Hash Dst +i_get_map_element_hash f? S c I S + +i_get_map_element Fail Src=c Key Dst => \ + move Src x | i_get_map_element Fail x Key Dst +i_get_map_element f? S S S + +# +# 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 => \ + i_unary_minus Src Fail 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/3 instruction (in bodies, not in guards). +# + +gen_plus p Live Int=i Reg=d Dst => \ + increment(Reg, Int, Dst) +gen_plus p Live Reg=d Int=i Dst => \ + increment(Reg, Int, Dst) + +gen_minus p Live Reg=d Int=i Dst | negation_is_small(Int) => \ + increment_from_minus(Reg, Int, Dst) + +# +# Arithmetic instructions. +# + +# It is OK to swap arguments for '+' in a guard. It is also +# OK to turn minus into plus in a guard. +gen_plus Fail=f Live S1=c S2 Dst => i_plus S2 S1 Fail Dst +gen_minus Fail=f Live S1 S2=i Dst | negation_is_small(S2) => \ + plus_from_minus(Fail, Live, S1, S2, Dst) + +gen_plus Fail Live S1 S2 Dst => i_plus S1 S2 Fail Dst + +gen_minus Fail Live S1 S2 Dst => i_minus S1 S2 Fail Dst + +gc_bif2 Fail Live u$bif:erlang:stimes/2 S1 S2 Dst => \ + i_times Fail S1 S2 Dst + +gc_bif2 Fail Live u$bif:erlang:div/2 S1 S2 Dst => \ + i_m_div Fail S1 S2 Dst + +# Fused 'rem'/'div' pair. +gc_bif2 Fail Live u$bif:erlang:rem/2 LHS RHS Remainder | \ + gc_bif2 A B u$bif:erlang:intdiv/2 LHS RHS Quotient | \ + distinct(LHS, Remainder) | \ + distinct(RHS, Remainder) => \ + i_rem_div LHS RHS Fail Remainder Quotient + +# As above but with a `line` in between +gc_bif2 Fail Live u$bif:erlang:rem/2 LHS RHS Remainder | \ + line Loc | \ + gc_bif2 A B u$bif:erlang:intdiv/2 LHS RHS Quotient | \ + distinct(LHS, Remainder) | \ + distinct(RHS, Remainder) => \ + i_rem_div LHS RHS Fail Remainder Quotient + +# Fused 'div'/'rem' pair +gc_bif2 Fail Live u$bif:erlang:intdiv/2 LHS RHS Quotient | \ + gc_bif2 A B u$bif:erlang:rem/2 LHS RHS Remainder | \ + distinct(LHS, Quotient) | \ + distinct(RHS, Quotient) => \ + i_div_rem LHS RHS Fail Quotient Remainder + +# As above but with a `line` in between +gc_bif2 Fail Live u$bif:erlang:intdiv/2 LHS RHS Quotient | \ + line Loc | \ + gc_bif2 A B u$bif:erlang:rem/2 LHS RHS Remainder | \ + distinct(LHS, Quotient) | \ + distinct(RHS, Quotient) => \ + i_div_rem LHS RHS Fail Quotient Remainder + +gc_bif2 Fail Live u$bif:erlang:intdiv/2 S1 S2 Dst => \ + i_int_div Fail S1 S2 Dst +gc_bif2 Fail Live u$bif:erlang:rem/2 S1 S2 Dst => \ + i_rem S1 S2 Fail Dst + +gc_bif2 Fail Live u$bif:erlang:bsl/2 S1 S2 Dst => \ + i_bsl S1 S2 Fail Dst +gc_bif2 Fail Live u$bif:erlang:bsr/2 S1 S2 Dst => \ + i_bsr S1 S2 Fail Dst + +gc_bif2 Fail Live u$bif:erlang:band/2 S1 S2 Dst => \ + i_band S1 S2 Fail Dst + +gc_bif2 Fail Live u$bif:erlang:bor/2 S1 S2 Dst => \ + i_bor Fail S1 S2 Dst + +gc_bif2 Fail Live u$bif:erlang:bxor/2 S1 S2 Dst => \ + i_bxor Fail S1 S2 Dst + +gc_bif1 Fail Live u$bif:erlang:bnot/1 Src Dst => \ + i_bnot Fail Src Dst + +i_increment S W d + +i_plus s s j? d +i_minus s s j? d + +i_unary_minus s j? d + +i_times j? s s d + +i_m_div j? s s d + +i_rem_div s s j? d d +i_div_rem s s j? d d +i_int_div j? s s d +i_rem s s j? d + +i_bsl s s j? d +i_bsr s s j? d + +i_band s s j? d +i_bor j? s s d +i_bxor j? s s d + +i_bnot j? s 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 + +# +# Handle the length/1 guard BIF specially to make it trappable. +# + +gc_bif1 Fail=j Live u$bif:erlang:length/1 Src Dst => \ + i_length_setup Fail Live Src | i_length Fail Live Dst + +i_length_setup j? t s +i_length j? t d + +# +# Guard BIFs. +# +gc_bif1 Fail Live Bif Src Dst => i_bif1 Src Fail Bif Dst +gc_bif2 Fail Live Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst +gc_bif3 Fail Live Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Dst + +# +# 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. +# +# Superseded in OTP 24 by 'recv_marker_reserve' and friends. +# + +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 +raw_raise + +# +# Specialized move instructions. Since they don't require a second +# instruction, we have intentionally placed them after any other +# transformation rules that starts with a move instruction in order to +# produce better code for the transformation engine. +# + +move n D=y => init D + +# +# OTP 24 +# + +recv_marker_reserve S +recv_marker_bind S S +recv_marker_clear S +recv_marker_use S |