Export * erts_suspend_process_on_pending_purge_lambda(Process *c_p) { erts_smp_mtx_lock(&purge_state.mtx); if (is_value(purge_state.module)) { /* * The process c_p is about to call a fun in the code * that we are trying to purge. Suspend it and call * erts_code_purger:pending_purge_lambda/3. The process * will be resumed when the purge completes or aborts, * and will then try to do the call again. */ if (purge_state.sp_ix >= purge_state.sp_size) { Eterm *sprocs; purge_state.sp_size += 100; sprocs = erts_alloc(ERTS_ALC_T_PURGE_DATA, (sizeof(ErlFunEntry *) * purge_state.sp_size)); sys_memcpy((void *) sprocs, (void *) purge_state.sprocs, purge_state.sp_ix*sizeof(ErlFunEntry *)); if (purge_state.sprocs != &purge_state.def_sprocs[0]) erts_free(ERTS_ALC_T_PURGE_DATA, purge_state.sprocs); purge_state.sprocs = sprocs; } purge_state.sprocs[purge_state.sp_ix++] = c_p->common.id; erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); ERTS_VBUMP_ALL_REDS(c_p); } erts_smp_mtx_unlock(&purge_state.mtx); return purge_state.pending_purge_lambda; }
/* * Calller _must_ yield if we return 0 */ int erts_try_seize_code_write_permission(Process* c_p) { int success; #ifdef ERTS_SMP ASSERT(!erts_smp_thr_progress_is_blocking()); /* to avoid deadlock */ #endif ASSERT(c_p != NULL); erts_smp_mtx_lock(&code_write_permission_mtx); success = (code_writing_process == NULL); if (success) { code_writing_process = c_p; #ifdef ERTS_ENABLE_LOCK_CHECK erts_tsd_set(has_code_write_permission, (void *) 1); #endif } else { /* Already locked */ struct code_write_queue_item* qitem; ASSERT(code_writing_process != c_p); qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem)); qitem->p = c_p; erts_proc_inc_refc(c_p); qitem->next = code_write_queue; code_write_queue = qitem; erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); } erts_smp_mtx_unlock(&code_write_permission_mtx); return success; }
static int try_seize_update_permission(Process* c_p) { int success; ASSERT(!erts_thr_progress_is_blocking()); /* to avoid deadlock */ ASSERT(c_p != NULL); erts_mtx_lock(&update_table_permission_mtx); ASSERT(updater_process != c_p); success = (updater_process == NULL); if (success) { updater_process = c_p; } else { struct update_queue_item* qitem; qitem = erts_alloc(ERTS_ALC_T_PERSISTENT_LOCK_Q, sizeof(*qitem)); qitem->p = c_p; erts_proc_inc_refc(c_p); qitem->next = update_queue; update_queue = qitem; erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); } erts_mtx_unlock(&update_table_permission_mtx); return success; }
static Eterm staging_epilogue(Process* c_p, int commit, Eterm res, int is_blocking, struct m* loaded, int nloaded) { #ifdef ERTS_SMP if (is_blocking || !commit) #endif { if (commit) { erts_end_staging_code_ix(); erts_commit_staging_code_ix(); if (loaded) { int i; for (i=0; i < nloaded; i++) { set_default_trace_pattern(loaded[i].module); } } } else { erts_abort_staging_code_ix(); } if (loaded) { erts_free(ERTS_ALC_T_LOADER_TMP, loaded); } if (is_blocking) { erts_smp_thr_progress_unblock(); erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); } erts_release_code_write_permission(); return res; } #ifdef ERTS_SMP else { ErtsThrPrgrVal later; ASSERT(is_value(res)); if (loaded) { erts_free(ERTS_ALC_T_LOADER_TMP, loaded); } erts_end_staging_code_ix(); /* * Now we must wait for all schedulers to do a memory barrier before * we can activate and let them access the new staged code. This allows * schedulers to read active code_ix in a safe way while executing * without any memory barriers at all. */ later = erts_thr_progress_later(); erts_thr_progress_wakeup(c_p->scheduler_data, later); erts_notify_code_ix_activation(c_p, later); erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); /* * handle_code_ix_activation() will do the rest "later" * and resume this process to return 'res'. */ ERTS_BIF_YIELD_RETURN(c_p, res); } #endif }
static void suspend_updater(Process* c_p) { #ifdef DEBUG ASSERT(c_p != NULL); erts_mtx_lock(&update_table_permission_mtx); ASSERT(updater_process == c_p); erts_mtx_unlock(&update_table_permission_mtx); #endif erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); }
BIF_RETTYPE erts_internal_copy_literals_2(BIF_ALIST_2) { ErtsCodeIndex code_ix; Eterm res = am_true; if (is_not_atom(BIF_ARG_1) || (am_true != BIF_ARG_2 && am_false != BIF_ARG_2)) { BIF_ERROR(BIF_P, BADARG); } if (!erts_try_seize_code_write_permission(BIF_P)) { ERTS_BIF_YIELD2(bif_export[BIF_erts_internal_copy_literals_2], BIF_P, BIF_ARG_1, BIF_ARG_2); } code_ix = erts_active_code_ix(); if (BIF_ARG_2 == am_true) { Module* modp = erts_get_module(BIF_ARG_1, code_ix); if (!modp || !modp->old.code_hdr) { res = am_false; goto done; } if (erts_clrange.ptr != NULL && !(BIF_P->static_flags & ERTS_STC_FLG_SYSTEM_PROC)) { res = am_aborted; goto done; } erts_clrange.ptr = modp->old.code_hdr->literals_start; erts_clrange.sz = modp->old.code_hdr->literals_end - erts_clrange.ptr; erts_clrange.pid = BIF_P->common.id; } else if (BIF_ARG_2 == am_false) { if (erts_clrange.pid != BIF_P->common.id) { res = am_false; goto done; } erts_clrange.ptr = NULL; erts_clrange.sz = 0; erts_clrange.pid = THE_NON_VALUE; } #ifdef ERTS_SMP ASSERT(committer_state.stager == NULL); committer_state.stager = BIF_P; erts_schedule_thr_prgr_later_op(copy_literals_commit, NULL, &committer_state.lop); erts_proc_inc_refc(BIF_P); erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); ERTS_BIF_YIELD_RETURN(BIF_P, am_true); #endif done: erts_release_code_write_permission(); BIF_RET(res); }
static void suspend_until_thr_prg(Process* p) { Binary* state_bin; ErtsFlxCtrWakeUpLaterInfo* info; state_bin = erts_create_magic_binary(sizeof(ErtsFlxCtrWakeUpLaterInfo), erts_flxctr_wait_dtor); info = ERTS_MAGIC_BIN_DATA(state_bin); info->process = p; erts_refc_inctest(&state_bin->intern.refc, 1); erts_suspend(p, ERTS_PROC_LOCK_MAIN, NULL); erts_proc_inc_refc(p); ERTS_VBUMP_ALL_REDS(p); erts_schedule_thr_prgr_later_op(thr_prg_wake_up_later, state_bin, &info->later_op); }
/* * Calller _must_ yield if we return 0 */ int erts_try_seize_code_write_permission(Process* c_p) { int success; #ifdef ERTS_SMP ASSERT(!erts_smp_thr_progress_is_blocking()); /* to avoid deadlock */ #endif erts_smp_mtx_lock(&the_code_ix_queue_lock); success = !the_code_ix_lock; if (success) { the_code_ix_lock = 1; } else { /* Already locked */ struct code_ix_queue_item* qitem; qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem)); qitem->p = c_p; erts_smp_proc_inc_refc(c_p); qitem->next = the_code_ix_queue; the_code_ix_queue = qitem; erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); } erts_smp_mtx_unlock(&the_code_ix_queue_lock); return success; }
BIF_RETTYPE erts_internal_port_command_3(BIF_ALIST_3) { BIF_RETTYPE res; Port *prt; int flags = 0; Eterm ref; if (is_not_nil(BIF_ARG_3)) { Eterm l = BIF_ARG_3; while (is_list(l)) { Eterm* cons = list_val(l); Eterm car = CAR(cons); if (car == am_force) flags |= ERTS_PORT_SIG_FLG_FORCE; else if (car == am_nosuspend) flags |= ERTS_PORT_SIG_FLG_NOSUSPEND; else BIF_RET(am_badarg); l = CDR(cons); } if (!is_nil(l)) BIF_RET(am_badarg); } prt = sig_lookup_port(BIF_P, BIF_ARG_1); if (!prt) BIF_RET(am_badarg); if (flags & ERTS_PORT_SIG_FLG_FORCE) { if (!(prt->drv_ptr->flags & ERL_DRV_FLAG_SOFT_BUSY)) BIF_RET(am_notsup); } #ifdef DEBUG ref = NIL; #endif switch (erts_port_output(BIF_P, flags, prt, prt->common.id, BIF_ARG_2, &ref)) { case ERTS_PORT_OP_CALLER_EXIT: case ERTS_PORT_OP_BADARG: case ERTS_PORT_OP_DROPPED: ERTS_BIF_PREP_RET(res, am_badarg); break; case ERTS_PORT_OP_BUSY: ASSERT(!(flags & ERTS_PORT_SIG_FLG_FORCE)); if (flags & ERTS_PORT_SIG_FLG_NOSUSPEND) ERTS_BIF_PREP_RET(res, am_false); else { erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, prt); ERTS_BIF_PREP_YIELD3(res, bif_export[BIF_erts_internal_port_command_3], BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3); } break; case ERTS_PORT_OP_BUSY_SCHEDULED: ASSERT(!(flags & ERTS_PORT_SIG_FLG_FORCE)); /* Fall through... */ case ERTS_PORT_OP_SCHEDULED: ASSERT(is_internal_ordinary_ref(ref)); ERTS_BIF_PREP_RET(res, ref); break; case ERTS_PORT_OP_DONE: ERTS_BIF_PREP_RET(res, am_true); break; default: ERTS_INTERNAL_ERROR("Unexpected erts_port_output() result"); break; } if (ERTS_PROC_IS_EXITING(BIF_P)) { KILL_CATCHES(BIF_P); /* Must exit */ ERTS_BIF_PREP_ERROR(res, BIF_P, EXC_ERROR); } return res; }
static Eterm staging_epilogue(Process* c_p, int commit, Eterm res, int is_blocking, struct m* mods, int nmods, int free_mods) { #ifdef ERTS_SMP if (is_blocking || !commit) #endif { if (commit) { int i; erts_end_staging_code_ix(); erts_commit_staging_code_ix(); for (i=0; i < nmods; i++) { if (mods[i].modp->curr.code_hdr) { set_default_trace_pattern(mods[i].module); } #ifdef HIPE hipe_redirect_to_module(mods[i].modp); #endif } } else { erts_abort_staging_code_ix(); } if (free_mods) { erts_free(ERTS_ALC_T_LOADER_TMP, mods); } if (is_blocking) { erts_smp_thr_progress_unblock(); erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); } erts_release_code_write_permission(); return res; } #ifdef ERTS_SMP else { ASSERT(is_value(res)); if (free_mods) { erts_free(ERTS_ALC_T_LOADER_TMP, mods); } erts_end_staging_code_ix(); /* * Now we must wait for all schedulers to do a memory barrier before * we can commit and let them access the new staged code. This allows * schedulers to read active code_ix in a safe way while executing * without any memory barriers at all. */ ASSERT(committer_state.stager == NULL); committer_state.stager = c_p; erts_schedule_thr_prgr_later_op(smp_code_ix_commiter, NULL, &committer_state.lop); erts_proc_inc_refc(c_p); erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL); /* * smp_code_ix_commiter() will do the rest "later" * and resume this process to return 'res'. */ ERTS_BIF_YIELD_RETURN(c_p, res); } #endif }
BIF_RETTYPE erts_internal_purge_module_2(BIF_ALIST_2) { if (BIF_P != erts_code_purger) BIF_ERROR(BIF_P, EXC_NOTSUP); if (is_not_atom(BIF_ARG_1)) BIF_ERROR(BIF_P, BADARG); switch (BIF_ARG_2) { case am_prepare: case am_prepare_on_load: { /* * Prepare for purge by marking all fun * entries referring to the code to purge * with "pending purge" markers. */ ErtsCodeIndex code_ix; Module* modp; Eterm res; if (is_value(purge_state.module)) BIF_ERROR(BIF_P, BADARG); code_ix = erts_active_code_ix(); /* * Correct module? */ modp = erts_get_module(BIF_ARG_1, code_ix); if (!modp) res = am_false; else { /* * Any code to purge? */ if (BIF_ARG_2 == am_prepare_on_load) { erts_rwlock_old_code(code_ix); } else { erts_rlock_old_code(code_ix); } if (BIF_ARG_2 == am_prepare_on_load) { ASSERT(modp->on_load); ASSERT(modp->on_load->code_hdr); purge_state.saved_old = modp->old; modp->old = *modp->on_load; erts_free(ERTS_ALC_T_PREPARED_CODE, (void *) modp->on_load); modp->on_load = 0; } if (!modp->old.code_hdr) res = am_false; else { BeamInstr* code; BeamInstr* end; erts_smp_mtx_lock(&purge_state.mtx); purge_state.module = BIF_ARG_1; erts_smp_mtx_unlock(&purge_state.mtx); res = am_true; code = (BeamInstr*) modp->old.code_hdr; end = (BeamInstr *)((char *)code + modp->old.code_length); erts_fun_purge_prepare(code, end); } if (BIF_ARG_2 == am_prepare_on_load) { erts_rwunlock_old_code(code_ix); } else { erts_runlock_old_code(code_ix); } } #ifndef ERTS_SMP BIF_RET(res); #else if (res != am_true) BIF_RET(res); else { /* * We'll be resumed when all schedulers are guaranteed * to see the "pending purge" markers that we've made on * all fun entries of the code that we are about to purge. * Processes trying to call these funs will be suspended * before calling the funs. That is we are guaranteed not * to get any more direct references into the code while * checking for such references... */ erts_schedule_thr_prgr_later_op(resume_purger, NULL, &purger_lop_data); erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); ERTS_BIF_YIELD_RETURN(BIF_P, am_true); } #endif } case am_abort: { /* * Soft purge that detected direct references into the code * we set out to purge. Abort the purge. */ if (purge_state.module != BIF_ARG_1) BIF_ERROR(BIF_P, BADARG); erts_fun_purge_abort_prepare(purge_state.funs, purge_state.fe_ix); #ifndef ERTS_SMP erts_fun_purge_abort_finalize(purge_state.funs, purge_state.fe_ix); finalize_purge_operation(BIF_P, 0); BIF_RET(am_false); #else /* * We need to restore the code addresses of the funs in * two stages in order to ensure that we do not get any * stale suspended processes due to the purge abort. * Restore address pointer (erts_fun_purge_abort_prepare); * wait for thread progress; clear pending purge address * pointer (erts_fun_purge_abort_finalize), and then * resume processes that got suspended * (finalize_purge_operation). */ erts_schedule_thr_prgr_later_op(finalize_purge_abort, NULL, &purger_lop_data); erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); ERTS_BIF_YIELD_RETURN(BIF_P, am_false); #endif } case am_complete: { ErtsCodeIndex code_ix; BeamInstr* code; Module* modp; int is_blocking = 0; Eterm ret; ErtsLiteralArea *literals = NULL; /* * We have no direct references into the code. * Complete to purge. */ if (purge_state.module != BIF_ARG_1) BIF_ERROR(BIF_P, BADARG); if (!erts_try_seize_code_write_permission(BIF_P)) { ERTS_BIF_YIELD2(bif_export[BIF_erts_internal_purge_module_2], BIF_P, BIF_ARG_1, BIF_ARG_2); } code_ix = erts_active_code_ix(); /* * Correct module? */ if ((modp = erts_get_module(BIF_ARG_1, code_ix)) == NULL) { ERTS_BIF_PREP_RET(ret, am_false); } else { erts_rwlock_old_code(code_ix); /* * Any code to purge? */ if (!modp->old.code_hdr) { ERTS_BIF_PREP_RET(ret, am_false); } else { /* * Unload any NIF library */ if (modp->old.nif != NULL || IF_HIPE(hipe_purge_need_blocking(modp))) { /* ToDo: Do unload nif without blocking */ erts_rwunlock_old_code(code_ix); erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); erts_smp_thr_progress_block(); is_blocking = 1; erts_rwlock_old_code(code_ix); if (modp->old.nif) { erts_unload_nif(modp->old.nif); modp->old.nif = NULL; } } /* * Remove the old code. */ ASSERT(erts_total_code_size >= modp->old.code_length); erts_total_code_size -= modp->old.code_length; code = (BeamInstr*) modp->old.code_hdr; erts_fun_purge_complete(purge_state.funs, purge_state.fe_ix); beam_catches_delmod(modp->old.catches, code, modp->old.code_length, code_ix); literals = modp->old.code_hdr->literal_area; modp->old.code_hdr->literal_area = NULL; erts_free(ERTS_ALC_T_CODE, (void *) code); modp->old.code_hdr = NULL; modp->old.code_length = 0; modp->old.catches = BEAM_CATCHES_NIL; erts_remove_from_ranges(code); #ifdef HIPE hipe_purge_module(modp, is_blocking); #endif ERTS_BIF_PREP_RET(ret, am_true); } if (purge_state.saved_old.code_hdr) { modp->old = purge_state.saved_old; purge_state.saved_old.code_hdr = 0; } erts_rwunlock_old_code(code_ix); } if (is_blocking) { erts_smp_thr_progress_unblock(); erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); } erts_release_code_write_permission(); finalize_purge_operation(BIF_P, ret == am_true); if (literals) { ErtsLiteralAreaRef *ref; ref = erts_alloc(ERTS_ALC_T_LITERAL_REF, sizeof(ErtsLiteralAreaRef)); ref->literal_area = literals; ref->next = NULL; erts_smp_mtx_lock(&release_literal_areas.mtx); if (release_literal_areas.last) { release_literal_areas.last->next = ref; release_literal_areas.last = ref; } else { release_literal_areas.first = ref; release_literal_areas.last = ref; } erts_smp_mtx_unlock(&release_literal_areas.mtx); erts_queue_message(erts_literal_area_collector, 0, erts_alloc_message(0, NULL), am_copy_literals, BIF_P->common.id); } return ret; } default: BIF_ERROR(BIF_P, BADARG); } }
BIF_RETTYPE erts_internal_release_literal_area_switch_0(BIF_ALIST_0) { ErtsLiteralArea *unused_la; ErtsLiteralAreaRef *la_ref; if (BIF_P != erts_literal_area_collector) BIF_ERROR(BIF_P, EXC_NOTSUP); erts_smp_mtx_lock(&release_literal_areas.mtx); la_ref = release_literal_areas.first; if (la_ref) { release_literal_areas.first = la_ref->next; if (!release_literal_areas.first) release_literal_areas.last = NULL; } erts_smp_mtx_unlock(&release_literal_areas.mtx); unused_la = ERTS_COPY_LITERAL_AREA(); if (!la_ref) { ERTS_SET_COPY_LITERAL_AREA(NULL); if (unused_la) { #ifdef ERTS_SMP ErtsLaterReleasLiteralArea *lrlap; lrlap = erts_alloc(ERTS_ALC_T_RELEASE_LAREA, sizeof(ErtsLaterReleasLiteralArea)); lrlap->la = unused_la; erts_schedule_thr_prgr_later_cleanup_op( later_release_literal_area, (void *) lrlap, &lrlap->lop, (sizeof(ErtsLaterReleasLiteralArea) + sizeof(ErtsLiteralArea) + ((unused_la->end - &unused_la->start[0]) - 1)*(sizeof(Eterm)))); #else erts_release_literal_area(unused_la); #endif } BIF_RET(am_false); } ERTS_SET_COPY_LITERAL_AREA(la_ref->literal_area); erts_free(ERTS_ALC_T_LITERAL_REF, la_ref); #ifdef ERTS_SMP erts_schedule_thr_prgr_later_op(complete_literal_area_switch, unused_la, &later_literal_area_switch); erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); ERTS_BIF_YIELD_RETURN(BIF_P, am_true); #else erts_release_literal_area(unused_la); BIF_RET(am_true); #endif }
static BIF_RETTYPE do_port_command(Process *BIF_P, Eterm arg1, Eterm arg2, Eterm arg3, Uint32 flags) { BIF_RETTYPE res; Port *p; /* Trace sched out before lock check wait */ if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) { trace_virtual_sched(BIF_P, am_out); } if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) { profile_runnable_proc(BIF_P, am_inactive); } p = id_or_name2port(BIF_P, arg1); if (!p) { if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) { trace_virtual_sched(BIF_P, am_in); } if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) { profile_runnable_proc(BIF_P, am_active); } BIF_ERROR(BIF_P, BADARG); } /* Trace port in, id_or_name2port causes wait */ if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) { trace_sched_ports_where(p, am_in, am_command); } if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) { profile_runnable_port(p, am_active); } ERTS_BIF_PREP_RET(res, am_true); if ((flags & ERTS_PORT_COMMAND_FLAG_FORCE) && !(p->drv_ptr->flags & ERL_DRV_FLAG_SOFT_BUSY)) { ERTS_BIF_PREP_ERROR(res, BIF_P, EXC_NOTSUP); } else if (!(flags & ERTS_PORT_COMMAND_FLAG_FORCE) && p->status & ERTS_PORT_SFLG_PORT_BUSY) { if (flags & ERTS_PORT_COMMAND_FLAG_NOSUSPEND) { ERTS_BIF_PREP_RET(res, am_false); } else { erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, p); if (erts_system_monitor_flags.busy_port) { monitor_generic(BIF_P, am_busy_port, p->id); } ERTS_BIF_PREP_YIELD3(res, bif_export[BIF_port_command_3], BIF_P, arg1, arg2, arg3); } } else { int wres; erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); ERTS_SMP_CHK_NO_PROC_LOCKS; wres = erts_write_to_port(BIF_P->id, p, arg2); erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); if (wres != 0) { ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG); } } if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) { trace_sched_ports_where(p, am_out, am_command); } if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) { profile_runnable_port(p, am_inactive); } erts_port_release(p); /* Trace sched in after port release */ if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) { trace_virtual_sched(BIF_P, am_in); } if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) { profile_runnable_proc(BIF_P, am_active); } if (ERTS_PROC_IS_EXITING(BIF_P)) { KILL_CATCHES(BIF_P); /* Must exit */ ERTS_BIF_PREP_ERROR(res, BIF_P, EXC_ERROR); } return res; }