From e67f514338568fbdc5e140958fb4273a9326c93d Mon Sep 17 00:00:00 2001 From: lucioleKi Date: Wed, 6 Nov 2024 14:51:50 +0100 Subject: [PATCH] compiler: Optimize most pure bifs to be guard bifs in try/catch When put in a try, calling a guard bif with no side-effects and 6 other bifs ([PR-9042](https://github.com/erlang/otp/pull/9042)) are optimized to remove the try/catch. However, most pure bifs can be safely optimized in the same way in order to gain performance. This PR introduces a new internal instruction `call_pseudo_guard_bif` that enables the compiler to remove the try/catch around the bif. The instruction is designed to call the bif in the same way, but having more optimized error-handling in the runtime system. Example code: try float_to_list(A, utf8) catch _:_ -> error end. Before, assembly for the bif call after optimizations: {label,2}. {allocate,1,1}. {'try',{y,0},{f,3}}. {line,[{location,"zlc.erl",23}]}. {call_ext,1,{extfunc,erlang,float_to_list,1}}. {try_end,{y,0}}. {deallocate,1}. return. Now, assembly for the bif call after optimizations: {label,2}. {allocate,0,1}. {line,[{location,"zlc.erl",23}]}. {call_pseudo_guard_bif,1,{extfunc,erlang,float_to_list,1},{f,3}}. {deallocate,0}. return. Bifs that are optimized for try/catch in this change are listed in `pbif.tab`. --- erts/emulator/beam/emu/bif_instrs.tab | 96 +++++++++++++ erts/emulator/beam/emu/ops.tab | 9 ++ erts/emulator/beam/jit/arm/beam_asm.hpp | 8 +- .../beam/jit/arm/beam_asm_global.hpp.pl | 3 + erts/emulator/beam/jit/arm/instr_bif.cpp | 111 ++++++++++++--- .../beam/jit/arm/instr_guard_bifs.cpp | 3 +- erts/emulator/beam/jit/arm/ops.tab | 53 +++++--- .../beam/jit/x86/beam_asm_global.hpp.pl | 3 + erts/emulator/beam/jit/x86/instr_bif.cpp | 93 ++++++++++--- erts/emulator/beam/jit/x86/ops.tab | 18 +++ erts/emulator/beam/predicates.tab | 20 +++ erts/emulator/utils/beam_makeops | 55 +++++++- erts/emulator/utils/make_tables | 9 +- lib/compiler/src/Makefile | 4 +- lib/compiler/src/beam_disasm.erl | 4 + lib/compiler/src/beam_jump.erl | 2 + lib/compiler/src/beam_ssa.erl | 2 + lib/compiler/src/beam_ssa_codegen.erl | 11 +- lib/compiler/src/beam_ssa_opt.erl | 44 ++++-- lib/compiler/src/beam_ssa_type.erl | 13 +- lib/compiler/src/beam_utils.erl | 2 + lib/compiler/src/beam_validator.erl | 25 ++++ lib/compiler/src/genop.tab | 6 + lib/compiler/src/pbif.tab | 76 +++++++++++ lib/compiler/test/bif_SUITE.erl | 126 +++++++++++++++--- 25 files changed, 694 insertions(+), 102 deletions(-) create mode 100644 lib/compiler/src/pbif.tab diff --git a/erts/emulator/beam/emu/bif_instrs.tab b/erts/emulator/beam/emu/bif_instrs.tab index 161938179c6a..c2a5cdf43b42 100644 --- a/erts/emulator/beam/emu/bif_instrs.tab +++ b/erts/emulator/beam/emu/bif_instrs.tab @@ -127,6 +127,102 @@ i_bif_body.call(Bif, Dst) { goto post_error_handling; } +i_call_pseudo_guard_bif(Live, Bif, Exp, Fail) { + Export *export; + ErtsBifFunc bf; + + Eterm result; + ErlHeapFragment *live_hf_end; + + bf = (ErtsBifFunc) $Bif; + export = (Export*) $Exp; + + if (!((FCALLS - 1) > 0 || (FCALLS-1) > neg_o_reds)) { + /* + * If we have run out of reductions, do a context + * switch before calling the BIF. + */ + c_p->arity = GET_EXPORT_ARITY(export); + c_p->current = &export->info.mfa; + goto context_switch3; + } + + if (ERTS_UNLIKELY(export->is_bif_traced)) { + $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION); + $DISPATCH_EXPORT(export); + } + + ERTS_MSACC_SET_BIF_STATE_CACHED_X(GET_EXPORT_MODULE(export), bf); + + PRE_BIF_SWAPOUT(c_p); + ERTS_DBG_CHK_REDS(c_p, FCALLS); + c_p->fcalls = FCALLS - 1; + if (FCALLS <= 0) { + save_calls(c_p, export); + } + ASSERT(!ERTS_PROC_IS_EXITING(c_p)); + ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); + live_hf_end = c_p->mbuf; + ERTS_CHK_MBUF_SZ(c_p); + + ERTS_ASSERT_TRACER_REFS(&c_p->common); + + result = (*bf)(c_p, reg, I); + + ERTS_ASSERT_TRACER_REFS(&c_p->common); + + /* Only heavy BIFs may GC. */ + ASSERT(E == c_p->stop); + + ERTS_CHK_MBUF_SZ(c_p); + ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result)); + ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); + ERTS_HOLE_CHECK(c_p); + ERTS_REQ_PROC_MAIN_LOCK(c_p); + if (ERTS_IS_GC_AFTER_BIF_DESIRED(c_p)) { + Uint arity = GET_EXPORT_ARITY(export); + result = erts_gc_after_bif_call_lhf(c_p, live_hf_end, result, + reg, arity); + E = c_p->stop; + } + HTOP = HEAP_TOP(c_p); + FCALLS = c_p->fcalls; + PROCESS_MAIN_CHK_LOCKS(c_p); + ERTS_DBG_CHK_REDS(c_p, FCALLS); + + /* + * We have to update the cache if we are enabled in order + * to make sure no bookkeeping is done after we disabled + * msacc. We don't always do this as it is quite expensive. + */ + if (ERTS_MSACC_IS_ENABLED_CACHED_X()) { + ERTS_MSACC_UPDATE_CACHE_X(); + } + ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_EMULATOR); + if (ERTS_LIKELY(is_value(result))) { + x(0) = result; + CHECK_TERM(x(0)); + $NEXT0(); + } else if (c_p->freason == TRAP) { + /* + * Set the continuation pointer to return to next + * instruction after the trap (either by a return from + * erlang code or by nif_bif.epilogue() when the BIF + * is done). + */ + $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION); + SET_I(c_p->i); + $DISPATCH(); + } + + /* + * Error handling. SWAPOUT is not needed because it was done above. + */ + ASSERT(c_p->stop == E); + $FAIL($Fail); + //| -no_next +} + // // length/1 is the only guard BIF that does not execute in constant // time. Here follows special instructions to allow the calculation of diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index 0a5606d82948..c6246f012df4 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -1541,6 +1541,15 @@ gc_bif3 Fail=f _Live Bif S1 S2 S3 Dst => i_bif3 S3 S2 S1 Fail Bif Dst unsupported_guard_bif/3 unsupported_guard_bif _A _B _C | never() => _ +# +# Pseudo-guard BIFs +# + +call_pseudo_guard_bif Live Func Fail => + i_call_pseudo_guard_bif Live Func Func Fail + +i_call_pseudo_guard_bif t b e f + # # R13B03 # diff --git a/erts/emulator/beam/jit/arm/beam_asm.hpp b/erts/emulator/beam/jit/arm/beam_asm.hpp index 26415a2ae6da..2ddc2b741bd7 100644 --- a/erts/emulator/beam/jit/arm/beam_asm.hpp +++ b/erts/emulator/beam/jit/arm/beam_asm.hpp @@ -1243,6 +1243,12 @@ class BeamModuleAssembler : public BeamAssembler, const ArgWord &Bif, const ArgRegister &Dst); + void emit_i_bif_pure(const ArgWord &Bif, + const ArgExport &Exp, + const ArgWord &Live, + const ArgLabel &Fail, + const ArgRegister &Dst); + void emit_error(int code); void emit_error(int reason, const ArgSource &Src); @@ -1299,7 +1305,7 @@ class BeamModuleAssembler : public BeamAssembler, void emit_validate_unicode(Label next, Label fail, a64::Gp value); - void ubif_comment(const ArgWord &Bif); + void ubif_comment(const ArgWord &Bif, const ArgExport &Exp); void emit_cmp_immed_to_bool(arm::CondCode cc, const ArgSource &LHS, diff --git a/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl b/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl index 758303c1d312..f7279a3bf230 100644 --- a/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl +++ b/erts/emulator/beam/jit/arm/beam_asm_global.hpp.pl @@ -43,6 +43,7 @@ call_bif_shared call_light_bif_shared call_nif_yield_helper + call_pseudo_guard_bif_shared catch_end_shared call_nif_early call_nif_shared @@ -191,6 +192,8 @@ sub gen_list { void emit_i_length_common(Label fail, int state_size); + void emit_call_bif_common(bool return_error); + void emit_raise_badarg(const ErtsCodeMFA *mfa); void emit_bif_bit_size_helper(Label fail); diff --git a/erts/emulator/beam/jit/arm/instr_bif.cpp b/erts/emulator/beam/jit/arm/instr_bif.cpp index 99f00255b3c6..be8e7476efcc 100644 --- a/erts/emulator/beam/jit/arm/instr_bif.cpp +++ b/erts/emulator/beam/jit/arm/instr_bif.cpp @@ -30,11 +30,31 @@ extern "C" #include "erl_msacc.h" } -void BeamModuleAssembler::ubif_comment(const ArgWord &Bif) { +void BeamModuleAssembler::ubif_comment(const ArgWord &Bif, + const ArgExport &Exp) { if (logger.file()) { - ErtsCodeMFA *mfa = ubif2mfa((void *)Bif.get()); - if (mfa) { - comment("UBIF: %T/%d", mfa->function, mfa->arity); + BeamFile_ImportEntry *import; + const Export *exp; + bool is_pure; + + import = &beam->imports.entries[Exp.val]; + exp = erts_active_export_entry(import->module, + import->function, + import->arity); + + is_pure = exp->bif_number != -1 && + bif_table[exp->bif_number].kind == BIF_KIND_PURE; + + if (is_pure) { + comment("fake UBIF: %T:%T/%d", + import->module, + import->function, + import->arity); + } else { + ErtsCodeMFA *mfa = ubif2mfa((void *)Bif.get()); + if (mfa) { + comment("UBIF: %T/%d", mfa->function, mfa->arity); + } } } } @@ -108,12 +128,13 @@ void BeamGlobalAssembler::emit_i_bif_body_shared() { void BeamModuleAssembler::emit_i_bif1(const ArgSource &Src1, const ArgLabel &Fail, const ArgWord &Bif, + const ArgExport &Exp, const ArgRegister &Dst) { auto src1 = load_source(Src1); a.str(src1.reg, getXRef(0)); - ubif_comment(Bif); + ubif_comment(Bif, Exp); emit_i_bif(Fail, Bif, Dst); } @@ -121,12 +142,13 @@ void BeamModuleAssembler::emit_i_bif2(const ArgSource &Src1, const ArgSource &Src2, const ArgLabel &Fail, const ArgWord &Bif, + const ArgExport &Exp, const ArgRegister &Dst) { auto [src1, src2] = load_sources(Src1, TMP1, Src2, TMP2); a.stp(src1.reg, src2.reg, getXRef(0)); - ubif_comment(Bif); + ubif_comment(Bif, Exp); emit_i_bif(Fail, Bif, Dst); } @@ -135,6 +157,7 @@ void BeamModuleAssembler::emit_i_bif3(const ArgSource &Src1, const ArgSource &Src3, const ArgLabel &Fail, const ArgWord &Bif, + const ArgExport &Exp, const ArgRegister &Dst) { auto [src1, src2] = load_sources(Src1, TMP1, Src2, TMP2); auto src3 = load_source(Src3, TMP3); @@ -142,7 +165,7 @@ void BeamModuleAssembler::emit_i_bif3(const ArgSource &Src1, a.stp(src1.reg, src2.reg, getXRef(0)); a.str(src3.reg, getXRef(2)); - ubif_comment(Bif); + ubif_comment(Bif, Exp); emit_i_bif(Fail, Bif, Dst); } @@ -168,12 +191,13 @@ void BeamModuleAssembler::emit_i_bif(const ArgLabel &Fail, void BeamModuleAssembler::emit_nofail_bif1(const ArgSource &Src1, const ArgWord &Bif, + const ArgExport &Exp, const ArgRegister &Dst) { auto src1 = load_source(Src1); a.str(src1.reg, getXRef(0)); - ubif_comment(Bif); + ubif_comment(Bif, Exp); mov_arg(ARG4, Bif); fragment_call(ga->get_i_bif_guard_shared()); mov_arg(Dst, ARG1); @@ -182,12 +206,13 @@ void BeamModuleAssembler::emit_nofail_bif1(const ArgSource &Src1, void BeamModuleAssembler::emit_nofail_bif2(const ArgSource &Src1, const ArgSource &Src2, const ArgWord &Bif, + const ArgExport &Exp, const ArgRegister &Dst) { auto [src1, src2] = load_sources(Src1, TMP1, Src2, TMP2); a.stp(src1.reg, src2.reg, getXRef(0)); - ubif_comment(Bif); + ubif_comment(Bif, Exp); mov_arg(ARG4, Bif); fragment_call(ga->get_i_bif_guard_shared()); mov_arg(Dst, ARG1); @@ -364,13 +389,8 @@ static Eterm debug_call_light_bif(Process *c_p, /* It is important that the below code is as optimized as possible. * When doing any changes, make sure to look at the estone bif_dispatch - * benchmark to make sure you don't introduce any regressions. - * - * ARG3 = entry - * ARG4 = export entry - * ARG8 = BIF pointer - */ -void BeamGlobalAssembler::emit_call_light_bif_shared() { + * benchmark to make sure you don't introduce any regressions. */ +void BeamGlobalAssembler::emit_call_bif_common(bool return_error) { arm::Mem entry_mem = TMP_MEM1q, export_mem = TMP_MEM2q, mbuf_mem = TMP_MEM3q; @@ -528,11 +548,16 @@ void BeamGlobalAssembler::emit_call_light_bif_shared() { a.bind(error); { - /* raise_exception_shared expects current PC in ARG2 and MFA in - * ARG4. */ - a.ldp(ARG2, ARG4, entry_mem); - add(ARG4, ARG4, offsetof(Export, info.mfa)); - a.b(labels[raise_exception_shared]); + if (return_error) { + a.mov(XREG0, THE_NON_VALUE); + a.ret(a64::x30); + } else { + /* raise_exception_shared expects current PC in + * ARG2 and MFA in ARG4. */ + a.ldp(ARG2, ARG4, entry_mem); + add(ARG4, ARG4, offsetof(Export, info.mfa)); + a.b(labels[raise_exception_shared]); + } } } @@ -577,6 +602,30 @@ void BeamGlobalAssembler::emit_call_light_bif_shared() { } } +/* + * ARG3 = entry + * ARG4 = export entry + * ARG8 = BIF pointer + * + * If successful, the result is returned in XREG0. + * In case of error, an exception is raised. + */ +void BeamGlobalAssembler::emit_call_light_bif_shared() { + emit_call_bif_common(false); +} + +/* + * ARG3 = entry + * ARG4 = export entry + * ARG8 = BIF pointer + * + * If successful, the result is returned in XREG0. + * In case of error, XREG0 is THE_NON_VALUE on return. + */ +void BeamGlobalAssembler::emit_call_pseudo_guard_bif_shared() { + emit_call_bif_common(true); +} + void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif, const ArgExport &Exp) { Label entry = a.newLabel(); @@ -594,6 +643,26 @@ void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif, fragment_call(ga->get_call_light_bif_shared()); } +void BeamModuleAssembler::emit_i_call_pseudo_guard_bif(const ArgWord &Live, + const ArgWord &Bif, + const ArgExport &Exp, + const ArgLabel &Fail) { + Label entry = a.newLabel(); + BeamFile_ImportEntry *e = &beam->imports.entries[Exp.get()]; + + a.bind(entry); + + mov_arg(ARG4, Exp); + mov_arg(ARG8, Bif); + a.adr(ARG3, entry); + + if (logger.file()) { + comment("BIF: %T:%T/%d", e->module, e->function, e->arity); + } + fragment_call(ga->get_call_pseudo_guard_bif_shared()); + emit_branch_if_not_value(XREG0, resolve_beam_label(Fail, dispUnknown)); +} + void BeamModuleAssembler::emit_send() { Label entry = a.newLabel(); diff --git a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp index b75c9d839d7e..d45e142b4ca8 100644 --- a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp +++ b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp @@ -802,12 +802,13 @@ void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src, */ void BeamModuleAssembler::emit_bif_is_map_key(const ArgWord &Bif, + const ArgExport &Exp, const ArgLabel &Fail, const ArgSource &Key, const ArgSource &Src, const ArgRegister &Dst) { if (!exact_type(Src)) { - emit_i_bif2(Key, Src, Fail, Bif, Dst); + emit_i_bif2(Key, Src, Fail, Bif, Exp, Dst); return; } diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index bfd23151c99e..202d00ee58a5 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -789,8 +789,8 @@ bif2 Fail _Bif=u$bif:erlang:map_get/2 Src1 Src2=xy Dst=d => bif_map_get j s s d bif2 Fail Bif=u$bif:erlang:is_map_key/2 Key Map=xy Dst=d => - bif_is_map_key Bif Fail Key Map Dst -bif_is_map_key b j s s d + bif_is_map_key Bif Bif Fail Key Map Dst +bif_is_map_key b e j s s d bif2 _Fail _Bif=u$bif:erlang:max/2 Src1 Src2 Dst => bif_max Src1 Src2 Dst @@ -799,16 +799,16 @@ bif2 _Fail _Bif=u$bif:erlang:min/2 Src1 Src2 Dst => bif_max s s d bif_min 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 | never_fails(Bif) => nofail_bif1 S1 Bif Bif Dst +bif2 _Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif 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 +bif1 Fail Bif S1 Dst => i_bif1 S1 Fail Bif Bif Dst +bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Bif Dst -nofail_bif2 S1=d S2 Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst -nofail_bif2 S1=d S2 Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst -nofail_bif2 S1 S2 Bif Dst | is_ge_bif(Bif) => bif_is_ge S1 S2 Dst -nofail_bif2 S1 S2 Bif Dst | is_lt_bif(Bif) => bif_is_lt S1 S2 Dst +nofail_bif2 S1=d S2 Bif _Exp Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst +nofail_bif2 S1=d S2 Bif _Exp Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst +nofail_bif2 S1 S2 Bif _Exp Dst | is_ge_bif(Bif) => bif_is_ge S1 S2 Dst +nofail_bif2 S1 S2 Bif _Exp Dst | is_lt_bif(Bif) => bif_is_lt S1 S2 Dst i_get_hash c W d i_get s d @@ -817,12 +817,12 @@ self d node d -nofail_bif1 s b d -nofail_bif2 s s b d +nofail_bif1 s b e d +nofail_bif2 s s b e d -i_bif1 s j b d -i_bif2 s s j b d -i_bif3 s s s j b d +i_bif1 s j b e d +i_bif2 s s j b e d +i_bif3 s s s j b e d bif_is_eq_exact S s d bif_is_ne_exact S s d @@ -1319,12 +1319,20 @@ gc_bif1 Fail _Live _Bif=u$bif:erlang:map_size/1 Src Dst=d => bif_map_size Fail Src Dst bif_map_size j s d +# +# Pure BIFs. +# +#gc_bif1 Fail=f Live Bif S1 Dst | is_pure_bif(Bif) => +# i_bif1_pure Bif Bif Live Fail S1 Dst + +#i_bif1_pure b e t f s 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 +gc_bif1 Fail _Live Bif Src Dst => i_bif1 Src Fail Bif Bif Dst +gc_bif2 Fail _Live Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Bif Dst +gc_bif3 Fail _Live Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Bif Dst # # The following instruction is specially handled in beam_load.c @@ -1334,6 +1342,15 @@ gc_bif3 Fail _Live Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Dst unsupported_guard_bif/3 unsupported_guard_bif _A _B _C | never() => _ +# +# Pseudo-guard BIFs +# + +call_pseudo_guard_bif Live Func Fail => + i_call_pseudo_guard_bif Live Func Func Fail + +i_call_pseudo_guard_bif t b e f + # # R13B03 # diff --git a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl index 26b8f8791b18..5472014281e4 100755 --- a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl +++ b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl @@ -36,6 +36,7 @@ bs_init_bits_shared call_bif_shared call_light_bif_shared + call_pseudo_guard_bif_shared call_nif_early call_nif_shared call_nif_yield_helper @@ -191,6 +192,8 @@ sub gen_list { x86::Mem emit_i_length_common(Label fail, int state_size); + void emit_call_bif_common(bool return_error); + void emit_internal_hash_helper(); void emit_flatmap_get_element(); void emit_hashmap_get_element(); diff --git a/erts/emulator/beam/jit/x86/instr_bif.cpp b/erts/emulator/beam/jit/x86/instr_bif.cpp index 5b0efa534344..773a8e0ab226 100644 --- a/erts/emulator/beam/jit/x86/instr_bif.cpp +++ b/erts/emulator/beam/jit/x86/instr_bif.cpp @@ -385,12 +385,8 @@ static Eterm debug_call_light_bif(Process *c_p, /* It is important that the below code is as optimized as possible. * When doing any changes, make sure to look at the estone bif_dispatch * benchmark to make sure you don't introduce any regressions. - * - * ARG3 = entry - * ARG4 = export entry - * RET = BIF pointer */ -void BeamGlobalAssembler::emit_call_light_bif_shared() { +void BeamGlobalAssembler::emit_call_bif_common(bool return_error) { x86::Mem entry_mem = TMP_MEM1q, export_mem = TMP_MEM2q, mbuf_mem = TMP_MEM3q; @@ -524,6 +520,9 @@ void BeamGlobalAssembler::emit_call_light_bif_shared() { a.mov(getXRef(0), RET); emit_leave_frame(); + if (return_error) { + a.and_(RET, RET); /* Clear Z flag */ + } a.ret(); a.bind(trap); @@ -546,27 +545,33 @@ void BeamGlobalAssembler::emit_call_light_bif_shared() { a.bind(error); { - a.mov(ARG2, entry_mem); - a.mov(ARG4, export_mem); - a.add(ARG4, imm(offsetof(Export, info.mfa))); + if (return_error) { + emit_leave_frame(); + a.xor_(RETd, RETd); /* Set Z flag */ + a.ret(); + } else { + a.mov(ARG2, entry_mem); + a.mov(ARG4, export_mem); + a.add(ARG4, imm(offsetof(Export, info.mfa))); #if !defined(NATIVE_ERLANG_STACK) - /* Discard the continuation pointer as it will never be - * used. */ - emit_unwind_frame(); + /* Discard the continuation pointer as it will never be + * used. */ + emit_unwind_frame(); #endif - /* Overwrite the return address with the entry address to - * ensure that only the entry address ends up in the stack - * trace. */ - if (erts_frame_layout == ERTS_FRAME_LAYOUT_RA) { - a.mov(x86::qword_ptr(E), ARG2); - } else { - ASSERT(erts_frame_layout == ERTS_FRAME_LAYOUT_FP_RA); - a.mov(x86::qword_ptr(E, 8), ARG2); + /* Overwrite the return address with the entry address to + * ensure that only the entry address ends up in the stack + * trace. */ + if (erts_frame_layout == ERTS_FRAME_LAYOUT_RA) { + a.mov(x86::qword_ptr(E), ARG2); + } else { + ASSERT(erts_frame_layout == ERTS_FRAME_LAYOUT_FP_RA); + a.mov(x86::qword_ptr(E, 8), ARG2); + } + + a.jmp(labels[raise_exception_shared]); } - - a.jmp(labels[raise_exception_shared]); } } @@ -624,6 +629,30 @@ void BeamGlobalAssembler::emit_call_light_bif_shared() { } } +/* + * ARG3 = entry + * ARG4 = export entry + * RET = BIF pointer + * + * If successful, the result is returned in RET. + * In case of error, an exception is raised. + */ +void BeamGlobalAssembler::emit_call_light_bif_shared() { + emit_call_bif_common(false); +} + +/* + * ARG3 = entry + * ARG4 = export entry + * RET = BIF pointer + * + * If successful, the result is returned in RET. + * In case of error, Z-flag is set on return. + */ +void BeamGlobalAssembler::emit_call_pseudo_guard_bif_shared() { + emit_call_bif_common(true); +} + void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif, const ArgExport &Exp) { Label entry = a.newLabel(); @@ -642,6 +671,28 @@ void BeamModuleAssembler::emit_call_light_bif(const ArgWord &Bif, fragment_call(ga->get_call_light_bif_shared()); } +void BeamModuleAssembler::emit_i_call_pseudo_guard_bif(const ArgWord &Live, + const ArgWord &Bif, + const ArgExport &Exp, + const ArgLabel &Fail) { + Label entry = a.newLabel(); + + align_erlang_cp(); + a.bind(entry); + + mov_arg(ARG4, Exp); + a.mov(RET, imm(Bif.get())); + a.lea(ARG3, x86::qword_ptr(entry)); + + if (logger.file()) { + BeamFile_ImportEntry *e = &beam->imports.entries[Exp.get()]; + comment("BIF: %T:%T/%d", e->module, e->function, e->arity); + } + + fragment_call(ga->get_call_pseudo_guard_bif_shared()); + a.je(resolve_beam_label(Fail)); +} + void BeamModuleAssembler::emit_send() { Label entry = a.newLabel(); diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 1d6e0b726de0..201be5d9121e 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -1249,6 +1249,15 @@ gc_bif1 Fail _Live _Bif=u$bif:erlang:map_size/1 Src Dst=d => bif_map_size Fail Src Dst bif_map_size j s d +# +# "Pure" BIFs. +# + +#gc_bif1 Fail=f _Live Bif S1 Dst | is_pure_bif(Bif) => +# i_bif1_pure S1 Fail Bif Dst + +#i_bif1_pure s f? b d + # # Guard BIFs. # @@ -1264,6 +1273,15 @@ gc_bif3 Fail _Live Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Dst unsupported_guard_bif/3 unsupported_guard_bif _A _B _C | never() => _ +# +# Pseudo-guard BIFs +# + +call_pseudo_guard_bif Live Func Fail => + i_call_pseudo_guard_bif Live Func Func Fail + +i_call_pseudo_guard_bif t b e f + # # R13B03 # diff --git a/erts/emulator/beam/predicates.tab b/erts/emulator/beam/predicates.tab index 0c93fe45b09d..5194a16610d7 100644 --- a/erts/emulator/beam/predicates.tab +++ b/erts/emulator/beam/predicates.tab @@ -140,6 +140,26 @@ pred.is_heavy_bif(Bif) { return 0; } +// pred.is_pure_bif(Bif) { +// BeamFile_ImportEntry *import; +// const Export *export; + +// if (Bif.type != TAG_u || Bif.val >= S->beam.imports.count) { +// return 0; +// } + +// import = &S->beam.imports.entries[Bif.val]; +// export = erts_active_export_entry(import->module, +// import->function, +// import->arity); + +// if (export->bif_number != -1) { +// return bif_table[export->bif_number].kind == BIF_KIND_PURE; +// } + +// return 0; +// } + // Predicate to test whether all of the given new small map keys are literals pred.is_small_map_literal_keys(Size, Rest) { Uint pair_count = Size.val / 2; diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops index 9585ea569962..efb38e4a7a3d 100755 --- a/erts/emulator/utils/beam_makeops +++ b/erts/emulator/utils/beam_makeops @@ -281,6 +281,13 @@ foreach my $makes_no_sense ('f', 'j', 'o', 'q') { delete $construction_type{$makes_no_sense}; } +# +# Pseudo-guard BIFs. +# +my $in_pbifs_section = 0; +my @pbifs; +my %known_bifs; # Known BIFs based on bif.tab. + # # Generate bits. # @@ -532,6 +539,20 @@ while (<>) { next; } + # + # Handle %pbifs start and %pbifs end. + # + if (/^\%pbifs\s+(\S+)/) { + if ($1 eq 'start') { + $in_pbifs_section = 1; + } elsif ($1 eq 'end') { + $in_pbifs_section = 0; + } else { + error("pbifs: Illegal sub-directive '$1'"); + } + next; + } + # # Handle unrecognized % directives. # @@ -539,6 +560,29 @@ while (<>) { error("Unrecognized directive '%$1'"); } + # + # Handle pseudo-guard BIFs. + # + if ($in_pbifs_section && m@^([\w\d_]+):([\w\d_]+)/(\d+)@) { + my $pbif = "$1:$2:$3"; + unless (defined $known_bifs{$pbif}) { + error("BIF $1:$2/$3 is not defined as a 'bif' in bif.tab\n"); + } + push @pbifs, "$1:$2:$3"; + next; + } elsif (m@^bif\s+([\w\d_]+):([\w\d_]+)/(\d+)@) { + # This is a `bif` directive in bif.tab. + my $pbif = "$1:$2:$3"; + $known_bifs{$pbif} = 1; + next; + } elsif (m@^[hu]bif\s+[\w\d_']+:@) { + # Ignore `hbif and `ubif` directives in bif.tab. + next; + } elsif (m@^bif\s+[\w\d_']+:@) { + # Ignore `bif` directives with quoted names in bif.tab. + next; + } + # # Handle transformations. # @@ -1220,7 +1264,7 @@ sub compiler_output { comment('erlang'); print "-export([format_number/0]).\n"; - print "-export([opcode/2,opname/1]).\n"; + print "-export([opcode/2,opname/1,is_pbif/3]).\n"; print "\n"; print "-spec format_number() -> $BEAM_FORMAT_NUMBER.\n"; print "format_number() -> $BEAM_FORMAT_NUMBER.\n\n"; @@ -1239,7 +1283,14 @@ sub compiler_output { print "opname($i) -> {", quote($gen_opname[$i]), ",$gen_arity[$i]};\n"; } - print "opname(Number) -> erlang:error(badarg, [Number]).\n"; + print "opname(Number) -> erlang:error(badarg, [Number]).\n\n"; + + print "-spec is_pbif(module(), atom(), non_neg_integer()) -> boolean().\n"; + foreach my $pbif (@pbifs) { + my($mod,$name,$arity) = split(':', $pbif); + print "is_pbif($mod, $name, $arity) -> true;\n"; + } + print "is_pbif(_, _, _) -> false.\n"; # # Generate .hrl file. diff --git a/erts/emulator/utils/make_tables b/erts/emulator/utils/make_tables index ac8ec0269bdc..74bca39023b4 100755 --- a/erts/emulator/utils/make_tables +++ b/erts/emulator/utils/make_tables @@ -89,7 +89,8 @@ while (<>) { my($type, @args) = split; if ($type eq 'atom') { save_atoms(@args); - } elsif ($type eq 'bif' or $type eq 'ubif' or $type eq 'hbif') { + } elsif ($type eq 'bif' or $type eq 'ubif' or + $type eq 'hbif' or $type eq 'pbif') { if (@args > 2) { error("$type only allows two arguments"); } @@ -131,6 +132,9 @@ while (<>) { elsif ($type eq 'ubif') { $kind = 'BIF_KIND_GUARD'; } + elsif ($type eq 'pbif') { + $kind = 'BIF_KIND_PURE'; + } else { error("invalid bif_type: $type"); } @@ -221,7 +225,8 @@ typedef void *BifFunction; typedef enum { BIF_KIND_REGULAR, BIF_KIND_HEAVY, - BIF_KIND_GUARD + BIF_KIND_GUARD, + BIF_KIND_PURE, } BifKind; typedef struct bif_entry { diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 9b50016bb559..df2c8573d996 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -174,8 +174,8 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk $(EGEN)/beam_opcodes.erl $(EGEN)/beam_opcodes.hrl: $(EGEN)/OPCODES-GENERATED -$(EGEN)/OPCODES-GENERATED: genop.tab - $(gen_verbose)$(PERL) $(ERL_TOP)/erts/emulator/utils/beam_makeops -compiler -outdir $(EGEN) $< && echo $? >$(EGEN)/OPCODES-GENERATED +$(EGEN)/OPCODES-GENERATED: genop.tab $(ERL_TOP)/erts/emulator/beam/bif.tab pbif.tab + $(gen_verbose)$(PERL) $(ERL_TOP)/erts/emulator/utils/beam_makeops -compiler -outdir $(EGEN) $^ && echo $^ >$(EGEN)/OPCODES-GENERATED $(EBIN)/beam_asm.beam: $(ESRC)/beam_asm.erl $(EGEN)/beam_opcodes.hrl $(V_ERLC) $(ERL_COMPILE_FLAGS) -DCOMPILER_VSN='"$(VSN)"' -o$(EBIN) $< diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index dd63a7e058f3..37d3b257ad60 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -818,6 +818,10 @@ resolve_inst({bif2,Args},Imports,_,_) -> [F,Bif,A1,A2,Reg] = resolve_args(Args), {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports), {bif,BifName,F,[A1,A2],Reg}; +resolve_inst({call_pseudo_guard_bif,Args},Imports,_,_) -> + [F,Bif,Reg] = resolve_args(Args), + Func = lookup(Bif+1,Imports), + {call_pseudo_guard_bif,F,Func,Reg}; resolve_inst({allocate,[{u,X0},{u,X1}]},_,_,_) -> {allocate,X0,X1}; resolve_inst({allocate_heap,[{u,X0},{u,X1},{u,X2}]},_,_,_) -> diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 3a213ed440f7..79b24a675199 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -935,6 +935,8 @@ instr_labels({bif,_Name,Lbl,_As,_R}) -> do_instr_labels(Lbl); instr_labels({gc_bif,_Name,Lbl,_Live,_As,_R}) -> do_instr_labels(Lbl); +instr_labels({call_pseudo_guard_bif,_Arity,_Lbl,Fail}) -> + do_instr_labels(Fail); instr_labels({bs_create_bin,Lbl,_,_,_,_,_}) -> do_instr_labels(Lbl); instr_labels({put_map,Lbl,_Op,_Src,_Dst,_Live,_List}) -> diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index 1773ada4b458..9c84d963634b 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -239,7 +239,9 @@ no_side_effect(#b_set{op=Op}) -> can_be_guard_bif(M, F, A) -> case {M,F,A} of + {erlang, binary_to_atom, 1} -> true; {erlang, binary_to_atom, 2} -> true; + {erlang, binary_to_existing_atom, 1} -> true; {erlang, binary_to_existing_atom, 2} -> true; {erlang, list_to_atom, 1} -> true; {erlang, list_to_existing_atom, 1} -> true; diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index e5ff7da4c239..09cf2e7c5428 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -1224,9 +1224,18 @@ cg_block([#cg_set{op=catch_end,dst=Dst0,args=Args0}|Is], Context, St0) -> [Dst,Reg,{x,0}] = beam_args([Dst0|Args0], St0), {Is0,St} = cg_block(Is, Context, St0), {[{catch_end,Reg}|copy({x,0}, Dst)++Is0],St}; +cg_block([#cg_set{anno=#{pseudo_bif := true}=Anno,op=call,dst=Dst0,args=[Func0|Args0]}, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + [Dst|Args] = beam_args([Dst0|Args0], St), + #b_remote{mod=Mod0,name=Name0,arity=Arity} = Func0, + {{atom,Mod},{atom,Name}} = {beam_arg(Mod0, St),beam_arg(Name0, St)}, + Func = {extfunc,Mod,Name,Arity}, + Line = call_line(body, Func, Anno), + Call = [{call_pseudo_guard_bif,Arity,Func,Fail}|copy({x,0}, Dst)], + Is = setup_args(Args, Anno, none, St) ++ Line ++ Call, + {Is,St}; cg_block([#cg_set{op=call}=I, #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) -> - %% A call in try/catch block. cg_block([I], none, St); cg_block([#cg_set{op=match_fail}=I, #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) -> diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 82bde660df3b..db7d60e695eb 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -546,12 +546,16 @@ merge_tuple_update_1([], Tuple) -> %%% Splitting before call and make_fun instructions gives more opportunities %%% for sinking get_tuple_element instructions. %%% +%%% Splitting before new_try_tag gives more opportunities for optimizing +%%% try/catch. +%%% ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> P = fun(#b_set{op={bif,element}}) -> true; (#b_set{op=call}) -> true; (#b_set{op=bs_init_writable}) -> true; (#b_set{op=make_fun}) -> true; + (#b_set{op=new_try_tag}) -> true; (_) -> false end, RPO = beam_ssa:rpo(Blocks0), @@ -1853,8 +1857,8 @@ do_reduce_try([{L, Blk} | Bs]=Bs0, Ws0) -> end; true -> Ws1 = sets:del_element(L, Ws0), - #b_blk{is=Is0} = Blk, - case reduce_try_is(Is0, []) of + #b_blk{is=Is0,last=Last} = Blk, + case reduce_try_is(Is0, Last, []) of {safe,Is} -> %% This block does not execute any instructions %% that would require a try. Analyze successors. @@ -1877,25 +1881,26 @@ do_reduce_try([], Ws) -> true = sets:is_empty(Ws), %Assertion. []. -reduce_try_is([#b_set{op=kill_try_tag}|Is], Acc) -> +reduce_try_is([#b_set{op=kill_try_tag}|Is], _Last, Acc) -> %% Remove this kill_try_tag instruction. If there was a landingpad %% instruction in this block, it has already been removed. Preserve %% all other instructions in the block. {done,reverse(Acc, Is)}; -reduce_try_is([#b_set{op=extract}|_], _Acc) -> +reduce_try_is([#b_set{op=extract}|_], _Last, _Acc) -> %% The error reason is accessed. unsafe; -reduce_try_is([#b_set{op=landingpad}|Is], Acc) -> - reduce_try_is(Is, Acc); -reduce_try_is([#b_set{op={succeeded,body}}=I0|Is], Acc) -> +reduce_try_is([#b_set{op=landingpad}|Is], Last, Acc) -> + reduce_try_is(Is, Last, Acc); +reduce_try_is([#b_set{op={succeeded,body}}=I0|Is], Last, Acc) -> %% If we reached this point, it means that the previous instruction %% has no side effects. We must now convert the flavor of the %% succeeded to the `guard`, since the try/catch will be removed. I = I0#b_set{op={succeeded,guard}}, - reduce_try_is(Is, [I|Acc]); -reduce_try_is([#b_set{op=call,args=[#b_remote{mod=#b_literal{val=M}, + reduce_try_is(Is, Last, [I|Acc]); +reduce_try_is([#b_set{anno=Anno,op=call,args=[#b_remote{mod=#b_literal{val=M}, name=#b_literal{val=F}, arity=A}=R0|Args0]}=I0|Is], + Last, Acc) -> %% Rewrite binary_to_(existing_)atom/1 call to binary_to_(existing_)atom/2. {I1, Args1} = if {M, F, A} =:= {erlang, binary_to_atom, 1} orelse @@ -1908,19 +1913,30 @@ reduce_try_is([#b_set{op=call,args=[#b_remote{mod=#b_literal{val=M}, case beam_ssa:can_be_guard_bif(M, F, A) of true -> I = I1#b_set{op={bif,F},args=Args1}, - reduce_try_is(Is, [I|Acc]); - false -> unsafe + reduce_try_is(Is, Last, [I|Acc]); + false -> + case {beam_opcodes:is_pbif(M, F, A),Last} of + {true,#b_br{bool=#b_literal{}}} -> + %% This branch only goes in one + %% direction. Optimization could be unsafe. + unsafe; + {true,_} -> + I = I0#b_set{anno=Anno#{pseudo_bif => true}}, + reduce_try_is(Is, Last, [I|Acc]); + {false,_} -> + unsafe + end end; -reduce_try_is([#b_set{op=Op}=I|Is], Acc) -> +reduce_try_is([#b_set{op=Op}=I|Is], Last, Acc) -> IsSafe = case Op of phi -> true; _ -> beam_ssa:no_side_effect(I) end, case IsSafe of - true -> reduce_try_is(Is, [I|Acc]); + true -> reduce_try_is(Is, Last, [I|Acc]); false -> unsafe end; -reduce_try_is([], Acc) -> +reduce_try_is([], _Last, Acc) -> {safe,reverse(Acc)}. %% Removes try/catch expressions whose expressions will never throw. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index de4533f7a2d6..d06665b9f673 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -1042,8 +1042,17 @@ simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I, Sub#{ Dst => Lit }; #t_atom{elements=[false]} when Kind =:= guard -> %% Failing operations are only safe to remove in guards. - Lit = #b_literal{val=false}, - Sub#{ Dst => Lit }; + %% If the instruction is a call to a pseudo-guard bif, it might be + %% unsafe to remove the operation. + case Ds0 of + #{Arg := #b_set{anno=#{pseudo_bif := true}}} -> + Ts = Ts0#{ Dst => Type }, + Ds = Ds0#{ Dst => I }, + {I, Ts, Ds}; + #{} -> + Lit = #b_literal{val=false}, + Sub#{ Dst => Lit } + end; _ -> true = is_map_key(Arg, Ds0), %Assertion. diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index 9a85aaa4103c..583773114d44 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -92,6 +92,8 @@ replace_labels_1([{bif,Name,{f,Lbl},As,R}|Is], Acc, D, Fb) when Lbl =/= 0 -> replace_labels_1(Is, [{bif,Name,{f,label(Lbl, D, Fb)},As,R}|Acc], D, Fb); replace_labels_1([{gc_bif,Name,{f,Lbl},Live,As,R}|Is], Acc, D, Fb) when Lbl =/= 0 -> replace_labels_1(Is, [{gc_bif,Name,{f,label(Lbl, D, Fb)},Live,As,R}|Acc], D, Fb); +replace_labels_1([{call_pseudo_guard_bif,Live,Func,{f,Lbl}}|Is], Acc, D, Fb) when Lbl =/= 0 -> + replace_labels_1(Is, [{call_pseudo_guard_bif,Live,Func,{f,label(Lbl, D, Fb)}}|Acc], D, Fb); replace_labels_1([{call,Ar,{f,Lbl}}|Is], Acc, D, Fb) -> replace_labels_1(Is, [{call,Ar,{f,label(Lbl, D, Fb)}}|Acc], D, Fb); replace_labels_1([{call_fun2,{f,Lbl},Ar,Func}|Is], Acc, D, Fb) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 791f0bbb5bc0..bc5010b9fdf3 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -731,6 +731,8 @@ vi({bif,Op,{f,Fail},Ss0,Dst0}, Vst0) -> false -> validate_bif(bif, Op, Fail, Ss, Dst, Vst0, Vst0) end; +vi({call_pseudo_guard_bif,Live,Func,{f,Fail}}, Vst) -> + validate_pseudo_guard_bif(Func, Live, Fail, Vst); vi({gc_bif,Op,{f,Fail},Live,Ss0,Dst0}, Vst0) -> Ss = [unpack_typed_arg(Arg, Vst0) || Arg <- Ss0], Dst = unpack_typed_arg(Dst0, Vst0), @@ -1252,6 +1254,29 @@ validate_body_call(Func, Live, validate_body_call(_, _, #vst{current=#st{numy=NumY}}) -> error({allocated, NumY}). +%% Call a pseudo guard bif in a removed try/catch. +%% If the call fails, no exception should be produced. +%% A fail label must exist for a call that can fail. +validate_pseudo_guard_bif(Func, Live, Fail, Vst) -> + verify_y_init(Vst), + verify_live(Live, Vst), + verify_call_args(Func, Live, Vst), + + SuccFun = fun(SuccVst0) -> + {RetType, _, _} = call_types(Func, Live, SuccVst0), + true = RetType =/= none, %Assertion. + SuccVst = schedule_out(0, SuccVst0), + create_term(RetType, call, [], {x,0}, SuccVst) + end, + + case will_call_succeed(Func, Live, Vst) of + yes -> + SuccFun(Vst); + _ -> + assert_no_exception(Fail), + branch(Fail, Vst, SuccFun) + end. + init_try_catch_branch(Kind, Dst, Fail, Vst0) -> assert_no_exception(Fail), diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index fdf038c29dcb..e69d9ac4b28e 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -695,3 +695,9 @@ BEAM_FORMAT_NUMBER=0 ## @spec executable_line Location Index ## @doc Provide location for an executable line. 183: executable_line/2 + +# OTP 28 + +## @spec call_pseudo_guard_bif Arity Fun Fail +## @doc Call a pseudo-guard bif within try/catch. +184: call_pseudo_guard_bif/3 diff --git a/lib/compiler/src/pbif.tab b/lib/compiler/src/pbif.tab new file mode 100644 index 000000000000..23581c19d289 --- /dev/null +++ b/lib/compiler/src/pbif.tab @@ -0,0 +1,76 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2024. 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% +# + +%pbifs start + +erlang:atom_to_binary/2 +erlang:atom_to_list/1 +erlang:binary_to_float/1 +erlang:binary_to_list/1 +erlang:binary_to_list/3 +erlang:float_to_list/1 +erlang:float_to_binary/1 +erlang:integer_to_binary/1 +erlang:integer_to_binary/2 +erlang:integer_to_list/1 +erlang:integer_to_list/2 +erlang:iolist_size/1 +erlang:iolist_to_binary/1 +erlang:list_to_binary/1 +erlang:list_to_float/1 +erlang:list_to_pid/1 +erlang:list_to_tuple/1 +erlang:make_fun/3 +erlang:phash/2 +erlang:pid_to_list/1 +erlang:setelement/3 +erlang:split_binary/2 +erlang:tuple_to_list/1 + +ets:lookup_element/3 +ets:update_counter/3 +ets:update_element/3 + +math:acos/1 +math:acosh/1 +math:asin/1 +math:asinh/1 +math:atan/1 +math:atan2/2 +math:atanh/1 +math:ceil/1 +math:cos/1 +math:cosh/1 +math:erf/1 +math:erfc/1 +math:exp/1 +math:floor/1 +math:fmod/2 +math:log/1 +math:log2/1 +math:log10/1 +math:pow/2 +math:sin/1 +math:sinh/1 +math:sqrt/1 +math:tan/1 +math:tanh/1 + +%pbifs end diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl index 24cc12d7cf11..37f5fd5a425a 100644 --- a/lib/compiler/test/bif_SUITE.erl +++ b/lib/compiler/test/bif_SUITE.erl @@ -20,6 +20,7 @@ -module(bif_SUITE). -include_lib("syntax_tools/include/merl.hrl"). +-include_lib("stdlib/include/assert.hrl"). -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, @@ -28,7 +29,8 @@ cover_trim/1, head_tail/1, min_max/1, - non_throwing/1]). + non_throwing/1, + non_throwing_pure/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -45,7 +47,8 @@ groups() -> cover_trim, head_tail, min_max, - non_throwing + non_throwing, + non_throwing_pure ]}]. init_per_suite(Config) -> @@ -254,6 +257,7 @@ min_max(_Config) -> 105 = num_clamped_add(5), 105.0 = num_clamped_add(5.0), + 110 = num_clamped_add(a), 110 = num_clamped_add({a,b,c}), 110 = num_clamped_add({a,b,c}), @@ -295,23 +299,111 @@ num_clamped_add(A) -> min(max(A, 0), 10) + 100. non_throwing(_Config) -> - a = try binary_to_atom(<<"a">>) - catch _:_ -> [] - end, - l = try list_to_existing_atom([108]) - catch _:_ -> [] - end, - [] = try list_to_existing_atom([a]) - catch _:_ -> [] - end, - 'Erlang' = try binary_to_atom(<<"Erlang">>, unicode) - catch _:_ -> [] - end, - [] = try binary_to_existing_atom(a, unicode) - catch _:_ -> [] - end, + abc = thing_to_atom(~"abc"), + [] = thing_to_atom(a), + [] = thing_to_atom(42), + [] = thing_to_atom([a,b,c]), + + erlang = thing_to_existing_atom(~"erlang"), + [] = thing_to_existing_atom(~"not an existing atom"), + [] = thing_to_existing_atom(a), + + ok. + +thing_to_atom(Bin0) -> + Bin = id(Bin0), + Res = try + binary_to_atom(Bin) + catch + _:_ -> + [] + end, + Res = try + binary_to_atom(Bin, utf8) + catch + _:_ -> + [] + end, + if + is_atom(Res) -> + List = unicode:characters_to_list(Bin), + Res = try + list_to_atom(List) + catch + _:_ -> + [] + end; + true -> + Res + end. + +thing_to_existing_atom(Bin0) -> + Bin = id(Bin0), + Res = try + binary_to_existing_atom(Bin) + catch + _:_ -> + [] + end, + Res = try + binary_to_existing_atom(Bin, utf8) + catch + _:_ -> + [] + end, + if + is_atom(Res) -> + List = unicode:characters_to_list(Bin), + Res = try + list_to_existing_atom(List) + catch + _:_ -> + [] + end; + true -> + Res + end. + +non_throwing_pure(_Config) -> + [0] = bin2list(<<0>>), + error = bin2list(a), + + ~"xyz" = list2bin("xyz"), + error = list2bin(a), + + HugeBin = rand:bytes(1_000_000), + ?assertEqual(HugeBin, list2bin(bin2list(HugeBin))), + ok. +bin2list(A0) -> + A = id(A0), + + Res = try + binary_to_list(A) + catch + _:_ -> error + end, + Res = try + binary_to_list(A, 1, byte_size(A)) + catch + _:_ -> error + end, + Res = try + _ = binary_to_list(A), + binary_to_list(A) + catch + _:_ -> error + end, + Res. + +list2bin(A) -> + try + list_to_binary(A) + catch + _:_ -> error + end. + %%% %%% Common utilities. %%%