Eterm erts_load_nif()

in erts/emulator/beam/erl_nif.c [4341:4648]


Eterm erts_load_nif(Process *c_p, BeamInstr *I, Eterm filename, Eterm args)
{
    static const char bad_lib[] = "bad_lib";
    static const char upgrade[] = "upgrade";
    char* lib_name = NULL;
    void* handle = NULL;
    void* init_func = NULL;
    ErlNifEntry* entry = NULL;
    ErlNifEnv env;
    int i, err, encoding;
    Module* module_p;
    Eterm mod_atom;
    const Atom* mod_atomp;
    Eterm f_atom;
    ErtsCodeMFA* caller;
    ErtsSysDdllError errdesc = ERTS_SYS_DDLL_ERROR_INIT;
    Eterm ret = am_ok;
    int veto;
    int taint = 1;
    struct erl_module_nif* lib = NULL;
    struct erl_module_instance* this_mi;
    struct erl_module_instance* prev_mi;

    encoding = erts_get_native_filename_encoding();
    if (encoding == ERL_FILENAME_WIN_WCHAR) {
        /* Do not convert the lib name to utf-16le yet, do that in win32 specific code */
        /* since lib_name is used in error messages */
        encoding = ERL_FILENAME_UTF8;
    }
    lib_name = erts_convert_filename_to_encoding(filename, NULL, 0,
                                                 ERTS_ALC_T_TMP, 1, 0, encoding,
						 NULL, 0);
    if (!lib_name) {
        return THE_NON_VALUE;
    }

    /* Find calling module */
    caller = erts_find_function_from_pc(I);
    ASSERT(caller != NULL);
    mod_atom = caller->module;
    ASSERT(is_atom(mod_atom));
    module_p = erts_get_module(mod_atom, erts_active_code_ix());
    ASSERT(module_p != NULL);

    mod_atomp = atom_tab(atom_val(mod_atom));
    {
        ErtsStaticNifEntry* sne;
        sne = erts_static_nif_get_nif_init((char*)mod_atomp->name, mod_atomp->len);
        if (sne != NULL) {
            init_func = sne->nif_init;
            handle = init_func;
            taint = sne->taint;
        }
    }
    this_mi = &module_p->curr;
    prev_mi = &module_p->old;
    if (in_area(caller, module_p->old.code_hdr, module_p->old.code_length)) {
	ret = load_nif_error(c_p, "old_code", "Calling load_nif from old "
			     "module '%T' not allowed", mod_atom);
	goto error;
    } else if (module_p->on_load) {
	ASSERT(module_p->on_load->code_hdr->on_load_function_ptr);
	if (module_p->curr.code_hdr) {
	    prev_mi = &module_p->curr;
	} else {
	    prev_mi = &module_p->old;
	}
	this_mi = module_p->on_load;
    }

    if (this_mi->nif != NULL) {
        ret = load_nif_error(c_p,"reload","NIF library already loaded"
                             " (reload disallowed since OTP 20).");
    }
    else if (init_func == NULL &&
             (err=erts_sys_ddll_open(lib_name, &handle, &errdesc)) != ERL_DE_NO_ERROR) {
	const char slogan[] = "Failed to load NIF library";
	if (strstr(errdesc.str, lib_name) != NULL) {
	    ret = load_nif_error(c_p, "load_failed", "%s: '%s'", slogan, errdesc.str);
	}
	else {
	    ret = load_nif_error(c_p, "load_failed", "%s %s: '%s'", slogan, lib_name, errdesc.str);
	}
    }
    else if (init_func == NULL &&
	     erts_sys_ddll_load_nif_init(handle, &init_func, &errdesc) != ERL_DE_NO_ERROR) {
	ret  = load_nif_error(c_p, bad_lib, "Failed to find library init"
			      " function: '%s'", errdesc.str);
	
    }
    else if ((taint ? erts_add_taint(mod_atom) : 0,
	      (entry = erts_sys_ddll_call_nif_init(init_func)) == NULL)) {
	ret = load_nif_error(c_p, bad_lib, "Library init-call unsuccessful");
    }
    else if (entry->major > ERL_NIF_MAJOR_VERSION
             || (entry->major == ERL_NIF_MAJOR_VERSION
                 && entry->minor > ERL_NIF_MINOR_VERSION)) {
        char* fmt = "That '%T' NIF library needs %s or newer. Either try to"
            " recompile the NIF lib or use a newer erts runtime.";
        ret = load_nif_error(c_p, bad_lib, fmt, mod_atom, entry->min_erts);
    }
    else if (entry->major < ERL_NIF_MIN_REQUIRED_MAJOR_VERSION_ON_LOAD
	     || (entry->major==2 && entry->minor == 5)) { /* experimental maps */
	
        char* fmt = "That old NIF library (%d.%d) is not compatible with this "
            "erts runtime (%d.%d). Try recompile the NIF lib.";
        ret = load_nif_error(c_p, bad_lib, fmt, entry->major, entry->minor,
                             ERL_NIF_MAJOR_VERSION, ERL_NIF_MINOR_VERSION);
    }   
    else if (AT_LEAST_VERSION(entry, 2, 1)
	     && sys_strcmp(entry->vm_variant, ERL_NIF_VM_VARIANT) != 0) {
	ret = load_nif_error(c_p, bad_lib, "Library (%s) not compiled for "
			     "this vm variant (%s).",
			     entry->vm_variant, ERL_NIF_VM_VARIANT);
    }
    else if (!erts_is_atom_str((char*)entry->name, mod_atom, 1)) {
	ret = load_nif_error(c_p, bad_lib, "Library module name '%s' does not"
			     " match calling module '%T'", entry->name, mod_atom);
    }
    else {
        lib = create_lib(entry);
        entry = &lib->entry; /* Use a guaranteed modern lib entry from now on */

        lib->handle = handle;
        erts_refc_init(&lib->refc, 2);  /* Erlang code + NIF code */
        erts_refc_init(&lib->dynlib_refc, 1);
        ASSERT(opened_rt_list == NULL);
        lib->mod = module_p;

#ifdef BEAMASM
        lib->finish = erts_alloc(ERTS_ALC_T_NIF_FINISH,
                                 sizeof_ErtsNifFinish(entry->num_of_funcs));
#else
        lib->finish = erts_alloc(ERTS_ALC_T_NIF,
                                 sizeof_ErtsNifFinish(entry->num_of_funcs));
#endif
        lib->finish->nstubs_hashed = 0;

        erts_rwmtx_rwlock(&erts_nif_call_tab_lock);
        for (i=0; i < entry->num_of_funcs; i++) {
	    ErtsCodeInfo** ci_pp;
            ErtsCodeInfo* ci;
            ErlNifFunc* f = &entry->funcs[i];
            ErtsNifBeamStub* stub = &lib->finish->beam_stubv[i];

            stub->code_info = NULL; /* end marker in case we fail */

	    if (!erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1)
		|| (ci_pp = get_func_pp(this_mi->code_hdr, f_atom, f->arity))==NULL) {
		ret = load_nif_error(c_p,bad_lib,"Function not found %T:%s/%u",
				     mod_atom, f->name, f->arity);
                break;
	    }
            ci = *ci_pp;

	    if (f->flags != 0 &&
                f->flags != ERL_NIF_DIRTY_JOB_IO_BOUND &&
                f->flags != ERL_NIF_DIRTY_JOB_CPU_BOUND) {

                ret = load_nif_error(c_p, bad_lib, "Illegal flags field value %d for NIF %T:%s/%u",
                                     f->flags, mod_atom, f->name, f->arity);
                break;
	    }

            ASSERT(erts_codeinfo_to_code(ci_pp[1]) - erts_codeinfo_to_code(ci_pp[0])
                     >= BEAM_NATIVE_MIN_FUNC_SZ);

            ERTS_CT_ASSERT(BEAM_NATIVE_MIN_FUNC_SZ*8 >= sizeof(stub->beam));

            stub->code_info = ci;
            stub->info = *ci;
            if (hash_put(&erts_nif_call_tab, stub) != stub) {
                ret = load_nif_error(c_p, bad_lib, "Duplicate NIF entry for %T:%s/%u",
                                     mod_atom, f->name, f->arity);
                break;
            }
            lib->finish->nstubs_hashed++;

 #ifdef BEAMASM
            {
                /* See beam_asm.h for details on how the nif load trampoline works */
                void* normal_fptr, *dirty_fptr;
                if (f->flags) {
                    if (f->flags == ERL_NIF_DIRTY_JOB_IO_BOUND)
                        normal_fptr = static_schedule_dirty_io_nif;
                    else
                        normal_fptr = static_schedule_dirty_cpu_nif;
                    dirty_fptr = f->fptr;
                } else {
                    dirty_fptr = NULL;
                    normal_fptr = f->fptr;
                }
                beamasm_emit_call_nif(
                    ci, normal_fptr, lib, dirty_fptr,
                    (char*)&stub->info,
                    sizeof(stub->info) + sizeof(stub->prologue) + sizeof(stub->beam));
            }
#else
            stub->beam[0] = BeamOpCodeAddr(op_call_nif_WWW);
            stub->beam[2] = (BeamInstr) lib;
            if (f->flags) {
                stub->beam[3] = (BeamInstr) f->fptr;
                stub->beam[1] = (f->flags == ERL_NIF_DIRTY_JOB_IO_BOUND) ?
                    (BeamInstr) static_schedule_dirty_io_nif :
                    (BeamInstr) static_schedule_dirty_cpu_nif;
            }
            else
                stub->beam[1] = (BeamInstr) f->fptr;
#endif
        }
        erts_rwmtx_rwunlock(&erts_nif_call_tab_lock);
        ASSERT(lib->finish->nstubs_hashed == lib->entry.num_of_funcs);
    }

    if (ret != am_ok) {
	goto error;
    }

    /* Call load or upgrade:
     */

    env.mod_nif = lib;

    lib->priv_data = NULL;
    if (prev_mi->nif != NULL) { /**************** Upgrade ***************/
        void* prev_old_data = prev_mi->nif->priv_data;
        if (entry->upgrade == NULL) {
            ret = load_nif_error(c_p, upgrade, "Upgrade not supported by this NIF library.");
            goto error;
        }
        /*
         * Go single scheduler during upgrade callback.
         * Todo: Fix better solution with suspending callers and waiting for
         *       all calls to return (including dirty).
         *       Note that erts_thr_progress_block() will not block dirty NIFs.
         */
        erts_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
        erts_thr_progress_block();
        erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);
        erts_pre_nif(&env, c_p, lib, NULL);
        veto = entry->upgrade(&env, &lib->priv_data, &prev_mi->nif->priv_data, args);
        erts_post_nif(&env);
        erts_thr_progress_unblock();
        if (veto) {
            prev_mi->nif->priv_data = prev_old_data;
            ret = load_nif_error(c_p, upgrade, "Library upgrade-call unsuccessful (%d).", veto);
        }
    }
    else if (entry->load != NULL) { /********* Initial load ***********/
        erts_pre_nif(&env, c_p, lib, NULL);
        veto = entry->load(&env, &lib->priv_data, args);
        erts_post_nif(&env);
        if (veto) {
            ret = load_nif_error(c_p, "load", "Library load-call unsuccessful (%d).", veto);
        }
    }

    if (ret == am_ok) {
	/*
         * Everything ok, make NIF code callable.
	 */
	this_mi->nif = lib;
        prepare_opened_rt(lib);
        /*
         * The call table lock will make sure all NIFs and callbacks in module
         * are made accessible atomically.
         */
        erts_rwmtx_rwlock(&erts_nif_call_tab_lock);
        commit_opened_rt();
        patch_call_nif_early(entry, this_mi);
        erts_rwmtx_rwunlock(&erts_nif_call_tab_lock);

        cleanup_opened_rt();

        /*
         * Now we wait thread progress, to make sure no process is still
         * executing the beam code of the NIFs, before we can patch in the
         * final fast multi word call_nif_WWW instructions.
         */
        erts_refc_inc(&lib->refc, 2);
        erts_schedule_thr_prgr_later_op(load_nif_1st_finisher, lib,
                                        &lib->lop);
    }
    else {
    error:
	rollback_opened_resource_types();
	ASSERT(ret != am_ok);
        if (lib != NULL) {
            if (lib->finish != NULL) {
                erase_hashed_stubs(lib->finish);
#ifdef BEAMASM
                erts_free(ERTS_ALC_T_NIF_FINISH, lib->finish);
#else
                erts_free(ERTS_ALC_T_NIF, lib->finish);
#endif
            }
	    erts_free(ERTS_ALC_T_NIF, lib);
	}
	if (handle != NULL && !erts_is_static_nif(handle)) {
	    erts_sys_ddll_close(handle);
	}
	erts_sys_ddll_free_error(&errdesc);
    }

    erts_free(ERTS_ALC_T_TMP, lib_name);

    BIF_RET(ret);
}