From 64bcf5db63d73fe09298dac8cf5f6cad33fe7253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 12 Jan 2012 20:12:59 +0100 Subject: Break apart erlang:load_module/2 into two separate BIFs Introduce two new BIFs, erlang:prepare_loading/2 and erlang:finish_loading/1, and re-implement erlang:load_module/2 in Erlang code. We have two reasons for doing this: * To facilitate suspending a process if another process is already doing code loading. * In the future, we can implement parallel and atomic loading of several modules. Atomic loading works except for modules with on_load handlers. Because of that issue, erlang:finish_loading/2 will currently only accept a list with a single magic binary. --- erts/emulator/beam/beam_bif_load.c | 295 ++++++++++++++++++++++++++----------- erts/emulator/beam/beam_load.c | 21 +++ erts/emulator/beam/bif.tab | 9 +- erts/emulator/beam/global.h | 1 + erts/preloaded/ebin/erlang.beam | Bin 86368 -> 87236 bytes erts/preloaded/src/erlang.erl | 37 ++++- 6 files changed, 270 insertions(+), 93 deletions(-) diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c index 42642e103d..98e5ad1e3f 100644 --- a/erts/emulator/beam/beam_bif_load.c +++ b/erts/emulator/beam/beam_bif_load.c @@ -44,93 +44,6 @@ static int is_native(BeamInstr* code); static int any_heap_ref_ptrs(Eterm* start, Eterm* end, char* mod_start, Uint mod_size); static int any_heap_refs(Eterm* start, Eterm* end, char* mod_start, Uint mod_size); -Eterm -load_module_2(BIF_ALIST_2) -{ - Eterm reason; - Eterm* hp; - int sz; - byte* code; - Eterm res; - byte* temp_alloc = NULL; - Binary* magic; - int is_blocking = 0; - - if (is_not_atom(BIF_ARG_1)) { - error: - erts_free_aligned_binary_bytes(temp_alloc); - BIF_ERROR(BIF_P, BADARG); - } - if ((code = erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc)) == NULL) { - goto error; - } - hp = HAlloc(BIF_P, 3); - - /* - * Read the BEAM file and prepare the module for loading. - */ - magic = erts_alloc_loader_state(); - sz = binary_size(BIF_ARG_2); - reason = erts_prepare_loading(magic, BIF_P, BIF_P->group_leader, - &BIF_ARG_1, code, sz); - erts_free_aligned_binary_bytes(temp_alloc); - if (reason != NIL) { - res = TUPLE2(hp, am_error, reason); - BIF_RET(res); - } - - erts_lock_code_ix(); - for(;;) { - Module* modp; - erts_start_staging_code_ix(); - modp = erts_put_module(BIF_ARG_1); - if (!is_blocking) { - if (modp->curr.num_breakpoints > 0 - || modp->curr.num_traced_exports > 0 - || erts_is_default_trace_enabled()) { - /* we have tracing, retry single threaded */ - erts_abort_staging_code_ix(); - erts_unlock_code_ix(); - erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); - erts_smp_thr_progress_block(); - is_blocking = 1; - continue; - } - } - else if (modp->curr.num_breakpoints) { - erts_clear_module_break(modp); - ASSERT(modp->curr.num_breakpoints == 0); - } - reason = erts_finish_loading(magic, BIF_P, 0, &BIF_ARG_1); - break; - } - - if (reason != NIL) { - if (reason == am_on_load) { - erts_commit_staging_code_ix(); - } - else { - erts_abort_staging_code_ix(); - } - res = TUPLE2(hp, am_error, reason); - } else { - erts_commit_staging_code_ix(); - if (is_blocking) { - set_default_trace_pattern(BIF_ARG_1); - } - res = TUPLE2(hp, am_module, BIF_ARG_1); - } - - if (is_blocking) { - erts_smp_thr_progress_unblock(); - erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); - } - else { - erts_unlock_code_ix(); - } - BIF_RET(res); -} - BIF_RETTYPE purge_module_1(BIF_ALIST_1) { int purge_res; @@ -199,6 +112,214 @@ BIF_RETTYPE code_make_stub_module_3(BIF_ALIST_3) return res; } +BIF_RETTYPE +prepare_loading_2(BIF_ALIST_2) +{ + byte* temp_alloc = NULL; + byte* code; + Uint sz; + Binary* magic; + Eterm reason; + Eterm* hp; + Eterm res; + + if (is_not_atom(BIF_ARG_1)) { + error: + erts_free_aligned_binary_bytes(temp_alloc); + BIF_ERROR(BIF_P, BADARG); + } + if ((code = erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc)) == NULL) { + goto error; + } + + magic = erts_alloc_loader_state(); + sz = binary_size(BIF_ARG_2); + reason = erts_prepare_loading(magic, BIF_P, BIF_P->group_leader, + &BIF_ARG_1, code, sz); + erts_free_aligned_binary_bytes(temp_alloc); + if (reason != NIL) { + hp = HAlloc(BIF_P, 3); + res = TUPLE2(hp, am_error, reason); + BIF_RET(res); + } + hp = HAlloc(BIF_P, PROC_BIN_SIZE); + res = erts_mk_magic_binary_term(&hp, &MSO(BIF_P), magic); + BIF_RET(res); +} + +struct m { + Binary* code; + Eterm module; + Module* modp; + Uint exception; +}; + +static Eterm +exception_list(Process* p, Eterm tag, struct m* mp, Sint exceptions) +{ + Eterm* hp = HAlloc(p, 3 + 2*exceptions); + Eterm res = NIL; + + mp += exceptions - 1; + while (exceptions > 0) { + if (mp->exception) { + res = CONS(hp, mp->module, res); + hp += 2; + exceptions--; + } + mp--; + } + return TUPLE2(hp, tag, res); +} + + +BIF_RETTYPE +finish_loading_1(BIF_ALIST_1) +{ + int i; + int n; + struct m* p; + Uint exceptions; + Eterm res; + int is_blocking = 0; + + /* + * Validate the argument before we start loading; it must be a + * proper list where each element is a magic binary containing + * prepared (not previously loaded) code. + * + * First count the number of elements and allocate an array + * to keep the elements in. + */ + + n = list_length(BIF_ARG_1); + if (n == -1) { + BIF_ERROR(BIF_P, BADARG); + } + p = erts_alloc(ERTS_ALC_T_LOADER_TMP, n*sizeof(struct m)); + + /* + * We now know that the argument is a proper list. Validate + * and collect the binaries into the array. + */ + + for (i = 0; i < n; i++) { + Eterm* cons = list_val(BIF_ARG_1); + Eterm term = CAR(cons); + ProcBin* pb; + + if (!ERTS_TERM_IS_MAGIC_BINARY(term)) { + error: + erts_free(ERTS_ALC_T_LOADER_TMP, p); + BIF_ERROR(BIF_P, BADARG); + } + pb = (ProcBin*) binary_val(term); + p[i].code = pb->val; + p[i].module = erts_module_for_prepared_code(p[i].code); + if (p[i].module == NIL) { + goto error; + } + BIF_ARG_1 = CDR(cons); + } + + /* + * Since we cannot handle atomic loading of a group of modules + * if one or more of them uses on_load, we will only allow one + * element in the list. This limitation is intended to be + * lifted in the future. + */ + + if (n > 1) { + erts_free(ERTS_ALC_T_LOADER_TMP, p); + BIF_ERROR(BIF_P, SYSTEM_LIMIT); + } + + /* + * All types are correct. There cannot be a BADARG from now on. + * Before we can start loading, we must check whether any of + * the modules already has old code. To avoid a race, we must + * not allow other process to initiate a code loading operation + * from now on. + */ + + res = am_ok; + erts_lock_code_ix(); + erts_start_staging_code_ix(); + + for (i = 0; i < n; i++) { + p[i].modp = erts_put_module(p[i].module); + if (p[i].modp->curr.num_breakpoints > 0 || + p[i].modp->curr.num_traced_exports > 0 || + erts_is_default_trace_enabled()) { + erts_abort_staging_code_ix(); + erts_unlock_code_ix(); + erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); + erts_smp_thr_progress_block(); + is_blocking = 1; + break; + } + } + + if (is_blocking) { + for (i = 0; i < n; i++) { + if (p[i].modp->curr.num_breakpoints) { + erts_clear_module_break(p[i].modp); + ASSERT(p[i].modp->curr.num_breakpoints == 0); + } + } + } + + exceptions = 0; + for (i = 0; i < n; i++) { + p[i].exception = 0; + if (p[i].modp->curr.code && p[i].modp->old.code) { + p[i].exception = 1; + exceptions++; + } + } + + if (exceptions) { + res = exception_list(BIF_P, am_not_purged, p, exceptions); + erts_abort_staging_code_ix(); + } else { + /* + * Now we can load all code. This can't fail. + */ + + exceptions = 0; + for (i = 0; i < n; i++) { + Eterm mod; + Eterm retval; + + erts_refc_inc(&p[i].code->refc, 1); + retval = erts_finish_loading(p[i].code, BIF_P, 0, &mod); + ASSERT(retval == NIL || retval == am_on_load); + if (retval == am_on_load) { + p[i].exception = 1; + exceptions++; + } + } + + /* + * Check whether any module has an on_load_handler. + */ + + if (exceptions) { + res = exception_list(BIF_P, am_on_load, p, exceptions); + } + erts_commit_staging_code_ix(); + } + + erts_free(ERTS_ALC_T_LOADER_TMP, p); + if (!is_blocking) { + erts_unlock_code_ix(); + } else { + erts_smp_thr_progress_unblock(); + erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN); + } + BIF_RET(res); +} + BIF_RETTYPE check_old_code_1(BIF_ALIST_1) { diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c index a9246a84c1..cc4f5b5893 100644 --- a/erts/emulator/beam/beam_load.c +++ b/erts/emulator/beam/beam_load.c @@ -847,6 +847,27 @@ erts_alloc_loader_state(void) return magic; } +/* + * Return the module name (a tagged atom) for the prepared code + * in the magic binary, or NIL if the binary does not contain + * prepared code. + */ +int +erts_module_for_prepared_code(Binary* magic) +{ + LoaderState* stp; + + if (ERTS_MAGIC_BIN_DESTRUCTOR(magic) != loader_state_dtor) { + return NIL; + } + stp = ERTS_MAGIC_BIN_DATA(magic); + if (stp->code != 0) { + return stp->module; + } else { + return NIL; + } +} + static void free_loader_state(Binary* magic) { diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 8cc568b16c..7f874a2d9c 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -142,8 +142,6 @@ bif erlang:list_to_pid/1 bif 'erl.lang.proc':string_to_pid/1 ebif_string_to_pid_1 list_to_pid_1 bif erlang:list_to_tuple/1 bif 'erl.lang.tuple':from_list/1 ebif_list_to_tuple_1 -bif erlang:load_module/2 -bif 'erl.system.code':load/2 ebif_load_module_2 bif erlang:loaded/0 bif 'erl.system.code':loaded/0 ebif_loaded_0 bif erlang:localtime/0 @@ -812,6 +810,13 @@ bif erlang:check_old_code/1 # bif erlang:universaltime_to_posixtime/1 bif erlang:posixtime_to_universaltime/1 + +# +# New in R16B. +# +bif erlang:prepare_loading/2 +bif erlang:finish_loading/1 + # # Obsolete # diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 6bafff0d6e..53a73fd762 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -873,6 +873,7 @@ typedef struct { } FunctionInfo; Binary* erts_alloc_loader_state(void); +int erts_module_for_prepared_code(Binary* magic); Eterm erts_prepare_loading(Binary* loader_state, Process *c_p, Eterm group_leader, Eterm* modp, byte* code, Uint size); diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam index cfd2a50431..efee885c6e 100644 Binary files a/erts/preloaded/ebin/erlang.beam and b/erts/preloaded/ebin/erlang.beam differ diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 2c1821df40..01173ce4d6 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -82,7 +82,7 @@ -export([delete_module/1, demonitor/1, demonitor/2, display/1]). -export([display_nl/0, display_string/1, dist_exit/3, erase/0, erase/1]). -export([error/1, error/2, exit/1, exit/2, external_size/1]). --export([external_size/2, finish_after_on_load/2, float/1]). +-export([external_size/2, finish_after_on_load/2, finish_loading/1, float/1]). -export([float_to_list/1, fun_info/2, fun_to_list/1, function_exported/3]). -export([garbage_collect/0, garbage_collect/1]). -export([garbage_collect_message_area/0, get/0, get/1, get_keys/1]). @@ -101,7 +101,8 @@ -export([pid_to_list/1, port_close/1, port_command/2, port_command/3]). -export([port_connect/2, port_control/3, port_get_data/1]). -export([port_set_data/2, port_to_list/1, ports/0]). --export([posixtime_to_universaltime/1, pre_loaded/0, process_display/2]). +-export([posixtime_to_universaltime/1, pre_loaded/0, prepare_loading/2]). +-export([process_display/2]). -export([process_flag/3, process_info/1, processes/0, purge_module/1]). -export([put/2, raise/3, read_timer/1, ref_to_list/1, register/2]). -export([registered/0, resume_process/1, round/1, self/0, send_after/3]). @@ -627,6 +628,15 @@ external_size(_Term) -> external_size(_Term, _Options) -> erlang:nif_error(undefined). +%% finish_loading/2 +-spec erlang:finish_loading(PreparedCodeBinaries) -> ok | Error when + PreparedCodeBinaries :: [PreparedCodeBinary], + PreparedCodeBinary :: binary(), + ModuleList :: [module()], + Error :: {not_purged,ModuleList} | {on_load,ModuleList}. +finish_loading(_List) -> + erlang:nif_error(undefined). + %% finish_after_on_load/2 -spec erlang:finish_after_on_load(P1, P2) -> true when P1 :: atom(), @@ -1080,6 +1090,15 @@ ports() -> posixtime_to_universaltime(_P1) -> erlang:nif_error(undefined). +%% prepare_loading/2 +-spec erlang:prepare_loading(Module, Code) -> PreparedCode | {error, Reason} when + Module :: module(), + Code :: binary(), + PreparedCode :: binary(), + Reason :: bad_file. +prepare_loading(_Module, _Code) -> + erlang:nif_error(undefined). + %% pre_loaded/0 -spec pre_loaded() -> [module()]. pre_loaded() -> @@ -1547,8 +1566,18 @@ is_tuple(_Term) -> Module :: module(), Binary :: binary(), Reason :: badfile | not_purged | on_load. -load_module(_Module,_Binary) -> - erlang:nif_error(undefined). +load_module(Mod, Code) -> + case erlang:prepare_loading(Mod, Code) of + {error,_}=Error -> + Error; + Bin when erlang:is_binary(Bin) -> + case erlang:finish_loading([Bin]) of + ok -> + {module,Mod}; + {Error,[Mod]} -> + {error,Error} + end + end. -spec erlang:load_nif(Path, LoadInfo) -> ok | Error when Path :: string(), -- cgit v1.2.3