summaryrefslogtreecommitdiff
path: root/erts/emulator/beam/erl_fun.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/erl_fun.c')
-rw-r--r--erts/emulator/beam/erl_fun.c232
1 files changed, 168 insertions, 64 deletions
diff --git a/erts/emulator/beam/erl_fun.c b/erts/emulator/beam/erl_fun.c
index 17b3e12dd8..ae6116ec19 100644
--- a/erts/emulator/beam/erl_fun.c
+++ b/erts/emulator/beam/erl_fun.c
@@ -29,6 +29,12 @@
#include "hash.h"
#include "beam_common.h"
+#ifdef DEBUG
+# define IF_DEBUG(x) x
+#else
+# define IF_DEBUG(x)
+#endif
+
/* Container structure for fun entries, allowing us to start `ErlFunEntry` with
* a field other than its `HashBucket`. */
typedef struct erl_fun_entry_container {
@@ -78,22 +84,33 @@ void
erts_fun_info(fmtfn_t to, void *to_arg)
{
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
hash_info(to, to_arg, &erts_fun_table);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
}
int erts_fun_table_sz(void)
{
int sz;
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
sz = hash_table_sz(&erts_fun_table);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
+
return sz;
}
@@ -130,8 +147,8 @@ erts_put_fun_entry2(Eterm mod, int old_uniq, int old_index,
return &fc->entry;
}
-const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe) {
- ErtsCodePtr address = fe->dispatch.addresses[0];
+const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe, ErtsCodeIndex ix) {
+ ErtsCodePtr address = fe->dispatch.addresses[ix];
if (address != beam_unloaded_fun) {
return erts_find_function_from_pc(address);
@@ -140,16 +157,14 @@ const ErtsCodeMFA *erts_get_fun_mfa(const ErlFunEntry *fe) {
return NULL;
}
-void erts_set_fun_code(ErlFunEntry *fe, ErtsCodePtr address) {
- int i;
-
- for (i = 0; i < ERTS_ADDRESSV_SIZE; i++) {
- fe->dispatch.addresses[i] = address;
- }
+void erts_set_fun_code(ErlFunEntry *fe, ErtsCodeIndex ix, ErtsCodePtr address) {
+ /* Fun entries MUST NOT be updated during a purge! */
+ ASSERT(fe->pend_purge_address == NULL);
+ fe->dispatch.addresses[ix] = address;
}
-int erts_is_fun_loaded(const ErlFunEntry* fe) {
- return fe->dispatch.addresses[0] != beam_unloaded_fun;
+int erts_is_fun_loaded(const ErlFunEntry* fe, ErtsCodeIndex ix) {
+ return fe->dispatch.addresses[ix] != beam_unloaded_fun;
}
static void
@@ -165,38 +180,49 @@ void
erts_erase_fun_entry(ErlFunEntry* fe)
{
erts_fun_write_lock();
- /*
- * We have to check refc again since someone might have looked up
- * the fun entry and incremented refc after last check.
- */
- if (erts_refc_dectest(&fe->refc, -1) <= 0)
- {
- if (erts_is_fun_loaded(fe)) {
+
+ /* We have to check refc again since someone might have looked up
+ * the fun entry and incremented refc after last check. */
+ if (erts_refc_dectest(&fe->refc, -1) <= 0) {
+ ErtsCodeIndex code_ix = erts_active_code_ix();
+
+ if (erts_is_fun_loaded(fe, code_ix)) {
erts_exit(ERTS_ERROR_EXIT,
"Internal error: "
"Invalid reference count found on #Fun<%T.%d.%d>: "
" About to erase fun still referred by code.\n",
fe->module, fe->old_index, fe->old_uniq);
}
+
erts_erase_fun_entry_unlocked(fe);
}
+
erts_fun_write_unlock();
}
+struct fun_prepare_purge_args {
+ struct erl_module_instance* modp;
+ ErtsCodeIndex code_ix;
+};
+
static void fun_purge_foreach(ErlFunEntryContainer *fc,
- struct erl_module_instance* modp)
+ struct fun_prepare_purge_args *args)
{
- const char *fun_addr, *mod_start;
+ struct erl_module_instance* modp = args->modp;
ErlFunEntry *fe = &fc->entry;
+ const char *mod_start;
+ ErtsCodePtr fun_addr;
- fun_addr = (const char*)fe->dispatch.addresses[0];
+ fun_addr = fe->dispatch.addresses[args->code_ix];
mod_start = (const char*)modp->code_hdr;
- if (ErtsInArea(fun_addr, mod_start, modp->code_length)) {
- fe->pend_purge_address = fe->dispatch.addresses[0];
+ if (ErtsInArea((const char*)fun_addr, mod_start, modp->code_length)) {
+ ASSERT(fe->pend_purge_address == NULL);
+
+ fe->pend_purge_address = fun_addr;
ERTS_THR_WRITE_MEMORY_BARRIER;
- erts_set_fun_code(fe, beam_unloaded_fun);
+ fe->dispatch.addresses[args->code_ix] = beam_unloaded_fun;
erts_purge_state_add_fun(fe);
}
@@ -205,46 +231,68 @@ static void fun_purge_foreach(ErlFunEntryContainer *fc,
void
erts_fun_purge_prepare(struct erl_module_instance* modp)
{
- erts_fun_read_lock();
- hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_purge_foreach, modp);
- erts_fun_read_unlock();
+ struct fun_prepare_purge_args args = {modp, erts_active_code_ix()};
+
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
+ erts_fun_write_lock();
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_purge_foreach, &args);
+ erts_fun_write_unlock();
}
void
erts_fun_purge_abort_prepare(ErlFunEntry **funs, Uint no)
{
- Uint ix;
+ ErtsCodeIndex code_ix = erts_active_code_ix();
+ Uint fun_ix;
- for (ix = 0; ix < no; ix++) {
- ErlFunEntry *fe = funs[ix];
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
- if (fe->dispatch.addresses[0] == beam_unloaded_fun) {
- erts_set_fun_code(fe, fe->pend_purge_address);
- }
+ for (fun_ix = 0; fun_ix < no; fun_ix++) {
+ ErlFunEntry *fe = funs[fun_ix];
+
+ ASSERT(fe->dispatch.addresses[code_ix] == beam_unloaded_fun);
+ fe->dispatch.addresses[code_ix] = fe->pend_purge_address;
}
}
void
erts_fun_purge_abort_finalize(ErlFunEntry **funs, Uint no)
{
- Uint ix;
+ IF_DEBUG(ErtsCodeIndex code_ix = erts_active_code_ix();)
+ Uint fun_ix;
- for (ix = 0; ix < no; ix++) {
- funs[ix]->pend_purge_address = NULL;
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
+ for (fun_ix = 0; fun_ix < no; fun_ix++) {
+ ErlFunEntry *fe = funs[fun_ix];
+
+ /* The abort_prepare step should have set the active address to the
+ * actual one. */
+ ASSERT(fe->dispatch.addresses[code_ix] != beam_unloaded_fun);
+ fe->pend_purge_address = NULL;
}
}
void
erts_fun_purge_complete(ErlFunEntry **funs, Uint no)
{
+ IF_DEBUG(ErtsCodeIndex code_ix = erts_active_code_ix();)
Uint ix;
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+
for (ix = 0; ix < no; ix++) {
- ErlFunEntry *fe = funs[ix];
- fe->pend_purge_address = NULL;
- if (erts_refc_dectest(&fe->refc, 0) == 0)
- erts_erase_fun_entry(fe);
+ ErlFunEntry *fe = funs[ix];
+
+ ASSERT(fe->dispatch.addresses[code_ix] == beam_unloaded_fun);
+ fe->pend_purge_address = NULL;
+
+ if (erts_refc_dectest(&fe->refc, 0) == 0) {
+ erts_erase_fun_entry(fe);
+ }
}
+
ERTS_THR_WRITE_MEMORY_BARRIER;
}
@@ -292,14 +340,11 @@ ErlFunThing *erts_new_local_fun_thing(Process *p, ErlFunEntry *fe,
#ifdef DEBUG
{
- /* FIXME: This assertion can fail because it may point to new code that
- * has not been committed yet. This is an actual bug but the fix is too
- * too involved and risky to release in a patch.
- *
- * As this problem has existed since the introduction of funs and is
- * very unlikely to cause actual issues in the wild, we've decided to
- * postpone the fix until OTP 26. See OTP-18016 for details. */
- const ErtsCodeMFA *mfa = erts_get_fun_mfa(fe);
+ /* Note that `mfa` may be NULL if the fun is currently being purged. We
+ * ignore this since it's not an error and we only need `mfa` to
+ * sanity-check the arity at this point. If the fun is called while in
+ * this state, the `error_handler` module will take care of it. */
+ const ErtsCodeMFA *mfa = erts_get_fun_mfa(fe, erts_active_code_ix());
ASSERT(!mfa || funp->arity == mfa->arity - num_free);
ASSERT(arity == fe->arity);
}
@@ -312,6 +357,7 @@ ErlFunThing *erts_new_local_fun_thing(Process *p, ErlFunEntry *fe,
struct dump_fun_foreach_args {
fmtfn_t to;
void *to_arg;
+ ErtsCodeIndex code_ix;
};
static void
@@ -323,21 +369,27 @@ dump_fun_foreach(ErlFunEntryContainer *fc, struct dump_fun_foreach_args *args)
erts_print(args->to, args->to_arg, "Module: %T\n", fe->module);
erts_print(args->to, args->to_arg, "Uniq: %d\n", fe->old_uniq);
erts_print(args->to, args->to_arg, "Index: %d\n",fe->old_index);
- erts_print(args->to, args->to_arg, "Address: %p\n", fe->dispatch.addresses[0]);
- erts_print(args->to, args->to_arg, "Refc: %ld\n", erts_refc_read(&fe->refc, 1));
+ erts_print(args->to, args->to_arg, "Address: %p\n",
+ fe->dispatch.addresses[args->code_ix]);
+ erts_print(args->to, args->to_arg, "Refc: %ld\n",
+ erts_refc_read(&fe->refc, 1));
}
void
erts_dump_fun_entries(fmtfn_t to, void *to_arg)
{
- struct dump_fun_foreach_args args = {to, to_arg};
+ struct dump_fun_foreach_args args = {to, to_arg, erts_active_code_ix()};
int lock = !ERTS_IS_CRASH_DUMPING;
- if (lock)
- erts_fun_read_lock();
+ if (lock) {
+ erts_fun_read_lock();
+ }
+
hash_foreach(&erts_fun_table, (HFOREACH_FUN)dump_fun_foreach, &args);
- if (lock)
- erts_fun_read_unlock();
+
+ if (lock) {
+ erts_fun_read_unlock();
+ }
}
static HashValue
@@ -365,7 +417,9 @@ fun_cmp(ErlFunEntryContainer* obj1, ErlFunEntryContainer* obj2)
static ErlFunEntryContainer*
fun_alloc(ErlFunEntryContainer* template)
{
- ErlFunEntryContainer* obj;
+ ErlFunEntryContainer *obj;
+ ErtsDispatchable *disp;
+ ErtsCodeIndex ix;
obj = (ErlFunEntryContainer *) erts_alloc(ERTS_ALC_T_FUN_ENTRY,
sizeof(ErlFunEntryContainer));
@@ -374,7 +428,14 @@ fun_alloc(ErlFunEntryContainer* template)
erts_refc_init(&obj->entry.refc, -1);
- erts_set_fun_code(&obj->entry, beam_unloaded_fun);
+ disp = &obj->entry.dispatch;
+ for (ix = 0; ix < ERTS_NUM_CODE_IX; ix++) {
+ disp->addresses[ix] = beam_unloaded_fun;
+ }
+
+#ifdef BEAMASM
+ disp->addresses[ERTS_SAVE_CALLS_CODE_IX] = beam_save_calls_fun;
+#endif
obj->entry.pend_purge_address = NULL;
@@ -386,3 +447,46 @@ fun_free(ErlFunEntryContainer* obj)
{
erts_free(ERTS_ALC_T_FUN_ENTRY, (void *) obj);
}
+
+struct fun_stage_args {
+ ErtsCodeIndex src_ix;
+ ErtsCodeIndex dst_ix;
+};
+
+static void fun_stage_foreach(ErlFunEntryContainer *fc,
+ struct fun_stage_args *args)
+{
+ ErtsDispatchable *disp = &fc->entry.dispatch;
+
+ /* Fun entries MUST NOT be updated during a purge! */
+ ASSERT(fc->entry.pend_purge_address == NULL);
+
+ disp->addresses[args->dst_ix] = disp->addresses[args->src_ix];
+}
+
+IF_DEBUG(static ErtsCodeIndex debug_fun_load_ix = 0;)
+
+void erts_fun_start_staging(void)
+{
+ ErtsCodeIndex dst_ix = erts_staging_code_ix();
+ ErtsCodeIndex src_ix = erts_active_code_ix();
+ struct fun_stage_args args = {src_ix, dst_ix};
+
+ ERTS_LC_ASSERT(erts_has_code_stage_permission());
+ ASSERT(dst_ix != src_ix);
+ ASSERT(debug_fun_load_ix == ~0);
+
+ erts_fun_write_lock();
+ hash_foreach(&erts_fun_table, (HFOREACH_FUN)fun_stage_foreach, &args);
+ erts_fun_write_unlock();
+
+ IF_DEBUG(debug_fun_load_ix = dst_ix);
+}
+
+void erts_fun_end_staging(int commit)
+{
+ ERTS_LC_ASSERT((erts_active_code_ix() == erts_active_code_ix()) ||
+ erts_has_code_stage_permission());
+ ASSERT(debug_fun_load_ix == erts_staging_code_ix());
+ IF_DEBUG(debug_fun_load_ix = ~0);
+}