/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * 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. **/ package erjang; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import erjang.codegen.EFunCG; import erjang.m.erlang.ErlBif; import erjang.m.ets.EMatchSpec; import erjang.m.java.JavaObject; import kilim.Pausable; /** * */ public class EModuleManager { static Logger log = Logger.getLogger(EModuleManager.class.getName()); static private Map<EAtom, ModuleInfo> infos = new ConcurrentHashMap<EAtom, ModuleInfo>(); // static FunctionInfo undefined_function = null; static EAtom am_prep_stop = EAtom.intern("prep_stop"); static EAtom am___info__ = EAtom.intern("__info__"); static EAtom am_call = EAtom.intern("call"); static EAtom am_return_from = EAtom.intern("return_from"); static EAtom am_exception_from = EAtom.intern("exception_from"); static EAtom am_trace = EAtom.intern("trace"); static EAtom am_global = EAtom.intern("global"); static EAtom am_local = EAtom.intern("local"); static EAtom am_meta = EAtom.intern("meta"); static class FunctionInfo { private final FunID fun; /** * @param fun */ public FunctionInfo(FunID fun) { this.fun = fun; } EModule defining_module; EFun resolved_value; boolean is_exported; Collection<FunctionBinder> resolve_points = new HashSet<FunctionBinder>(); private EFun error_handler; private ClassLoader getModuleClassLoader() { if (defining_module != null) { return defining_module.getModuleClassLoader(); } else { return new EModuleClassLoader(null); } } /** * @param ref * @throws IllegalAccessException * @throws IllegalArgumentException */ synchronized void add_import(final FunctionBinder ref) { resolve_points.add(ref); if (is_exported && resolved_value != null) { // System.out.println("binding "+fun+" "+resolved_value+" -> "+ref); if (traceHandler == null) { ref.bind(resolved_value); } else { ref.bind(traceHandler.self); } } else { EFun h = getFunErrorHandler(); ref.bind(h); } } synchronized void add_internal(final FunctionBinder ref) throws Exception { resolve_points.add(ref); } synchronized void unbind() throws Exception { Collection<FunctionBinder> gone = null; for (FunctionBinder f : resolve_points) { if (f.bind(getFunErrorHandler()) == false) { if (gone == null) { gone = new ArrayList<>(); } gone.add(f); } } if (traceHandler != null) { traceHandler.target = getFunErrorHandler(); } if (gone != null) { resolve_points.removeAll(gone); } } boolean isImportedSomewhere() { return !resolve_points.isEmpty(); } private EFun getTargetFunction() { if (resolved_value != null) { return resolved_value; } else { return getFunErrorHandler(); } } private EFun getFunErrorHandler() { if (error_handler != null) { return error_handler; } error_handler = makeErrorHandler(); return error_handler; } private EFun makeErrorHandler() { return EFunCG.get_fun_with_handler(fun.module.getName(), fun.function.getName(), fun.arity, new EFunHandler() { @Override public String toString() { return "#EFunHandler<" + fun.toString() + ">"; } public EObject invoke(EProc proc, EObject[] args) throws Pausable { /** Get reference to error_handler:undefined_function/3 */ EFun uf = proc.undefined_function.resolved_value; /** this is just some debugging info to help understand downstream errors */ if (get_module_info(fun.module).is_loaded()) { if (Boolean.getBoolean("erjang.declare_missing_imports") && fun.function != am_prep_stop && fun.function != am___info__) log.log(Level.INFO, "MISSING " + fun); } else { log.log(Level.FINER, "resolving" + fun); } Class<?> c = null; try { c = Class.forName(fun.module.getName()); } catch (ClassNotFoundException e) { } if (c != null) { if (fun.function == ERT.am_new) { Constructor[] cons = c.getConstructors(); return JavaObject.choose_and_invoke_constructor(proc, args, cons); } else { Method[] methods = c.getMethods(); return JavaObject.choose_and_invoke_method(proc, null, fun.function, args, methods, true); } } if (uf == null) { if (!module_loaded(fun.module)) { try { EModuleLoader.find_and_load_module(fun.module.getName()); } catch (IOException e) { // we don't report this separately; it ends up causing an undefined below... } } if (resolved_value != null) { return resolved_value.invoke(proc, args); } /** this is just some debugging info to help understand downstream errors */ log.log(Level.INFO, "failed to load " + fun + " (" + proc.undefined_function + " not found)"); throw new ErlangUndefined(fun.module, fun.function, fun.arity); } else { ESeq arg_list = ESeq.fromArray(args); ESeq ufa = ESeq.fromArray(new EObject[]{ fun.module, fun.function, arg_list}); return ErlBif.apply_last(proc, uf, ufa); // return uf.apply(proc, ufa); } } }, getModuleClassLoader()); } /** * @param fun2 * @param value */ synchronized void add_export(EModule definer, FunID fun2, EFun value) throws Exception { this.is_exported = true; this.defining_module = definer; bind(value); } void bind(EFun value) { this.resolved_value = value; if (traceHandler == null) { Collection<FunctionBinder> gone = null; for (FunctionBinder f : resolve_points) { // System.out.println("binding " + fun2 + " " + value + " -> " + f); if (f.bind(value) == false) { if (gone == null) { gone = new ArrayList<>(); } gone.add(f); } } if (gone != null) { resolve_points.removeAll(gone); } } else { traceHandler.target = value; } } /** * @return * */ public EFun resolve() { if (traceHandler == null) { return getTargetFunction(); } else { return traceHandler.self; } } /** * @return */ public boolean exported() { return resolved_value != null && is_exported; } public void trace_pattern(EObject spec, TraceOpts opts) { if (traceHandler == null) { if (spec == ERT.FALSE) return; traceHandler = new TraceHandler(this); } else if (spec==ERT.FALSE) { // uninstall tracer // EFun target = traceHandler.target; traceHandler = null; bind(target); return; } traceHandler.apply(opts); } TraceHandler traceHandler; } static class TraceHandler implements EFunHandler { AtomicLong counter = new AtomicLong(0); EFun target; final EFun self; final FunID funID; private EMatchSpec match_spec; public void apply(TraceOpts spec) { this.match_spec = spec.match_spec; } TraceHandler(FunctionInfo h) { funID = h.fun; target = h.getTargetFunction(); self = EFunCG.get_fun_with_handler( funID.module.getName(), funID.function.getName(), funID.arity, this, TraceHandler.class.getClassLoader()); } @Override public EObject invoke(EProc proc, EObject[] args) throws Pausable { ESeq argList = EList.fromArray(args); EMatchSpec.TraceState state = before(proc, argList); if (state == null || (state.return_trace==false && state.exception_trace==false)) return ErlBif.apply_last(proc, target, argList); EObject result; try { result = target.invoke(proc, args); } catch (ErlangException e) { if (state.exception_trace && !proc.get_trace_flags().silent) { ETuple3 info = ETuple3.make_tuple(funID.module, funID.function, ERT.box(funID.arity)); ERT.do_trace(proc, am_exception_from, info, new ETuple2(e.getExClass(), e.reason())); } throw e; } if (state.return_trace && !proc.get_trace_flags().silent) { ETuple3 info = ETuple3.make_tuple(funID.module, funID.function, ERT.box(funID.arity)); ERT.do_trace(proc, am_return_from, info, result); } return result; } EMatchSpec.TraceState before(EProc proc, ESeq argList) throws Pausable { if (!proc.get_trace_flags().call) return null; EMatchSpec.TraceState state = null; if (match_spec==null || (state=match_spec.matches(proc, argList)) != null) { counter.incrementAndGet(); // send {trace, Pid, call, {M, F, Args}} EObject arg = (proc.get_trace_flags().arity) ? ERT.box(funID.arity) : argList; if (!proc.get_trace_flags().silent && (state==null||state.message != ERT.FALSE)) { ETuple3 info = ETuple3.make_tuple(funID.module, funID.function, arg); if ((state == null || state.message == ERT.TRUE)) ERT.do_trace(proc, am_call, info); else ERT.do_trace(proc, am_call, info, state.message); } } return state; } } static class ModuleInfo { protected static final EAtom am_badfun = EAtom.intern("badfun"); private EModule resident; private EAtom module; private EBinary module_md5 = empty_md5; Map<FunID, FunctionInfo> binding_points = new ConcurrentHashMap<FunID, FunctionInfo>(); /** * @param module */ public ModuleInfo(EAtom module) { this.module = module; } /** * @param fun * @param ref * @return * @throws Exception */ public void add_import(FunID fun, FunctionBinder ref) { FunctionInfo info = get_function_info(fun); info.add_import(ref); } public void add_internal(FunID fun, FunctionBinder ref) throws Exception { FunctionInfo info = get_function_info(fun); info.add_internal(ref); } synchronized FunctionInfo get_function_info(FunID fun) { FunctionInfo info = binding_points.get(fun); if (info == null) { binding_points.put(fun, info = new FunctionInfo(fun)); } return info; } /** * @param fun * @param value * @throws Exception */ public void add_export(EModule definer, FunID fun, EFun value) throws Exception { get_function_info(fun).add_export(definer, fun, value); } /** * @param eModule */ public void setModule(EModule eModule) { this.resident = eModule; } /** * @param fun * @return */ public EFun resolve(FunID fun) { return get_function_info(fun).resolve(); } /** * @param fun * @return */ public boolean exports(FunID fun) { return get_function_info(fun).exported(); } /** * @return */ public boolean is_loaded() { return resident != null; } public EAtom unload() throws Exception { if (resident == null) { return ERT.am_undefined; } java.lang.System.gc(); resident = null; ArrayList<FunID> unreferenced = new ArrayList<>(); for (FunctionInfo fi : this.binding_points.values()) { fi.unbind(); if (!fi.isImportedSomewhere()) { unreferenced.add(fi.fun); } } for (FunID fid : unreferenced) { binding_points.remove(fid); } this.resident = null; this.module_md5 = empty_md5; return ERT.TRUE; } /** * @return */ public synchronized ESeq get_attributes() { ESeq res; if (resident == null) res = ERT.NIL; else res = resident.attributes(); return res; } public synchronized ESeq get_compile() { ESeq res; if (resident == null) res = ERT.NIL; else res = resident.compile(); return res; } /** * */ public void warn_about_unresolved() { if (resident != null) { for (FunctionInfo fi : binding_points.values()) { if ((fi.resolved_value == null) && (fi.fun.module != this.module)) { log.log(Level.INFO, "unresolved after load: "+fi.fun); } } } } public ESeq get_exports() { ESeq rep = ERT.NIL; for (FunctionInfo fi : binding_points.values()) { if (fi.exported()) { rep = rep.cons(new ETuple2(fi.fun.function, ERT.box(fi.fun.arity))); } } return rep; } Map<Integer,EFunMaker> new_map = new HashMap<Integer, EFunMaker>(); Map<Long,EFunMaker> old_map = new HashMap<Long, EFunMaker>(); /** resolve a fun we got from a R5+ node * @param arity */ public EFun resolve(EPID pid, EBinary md5, int index, final int old_uniq, final int old_index, int arity, EObject[] freevars) { if (resident == null) { throw new NotImplemented("calling FUN before module "+this.module+" is loaded"); } EFunMaker maker = new_map.get(index); if (maker == null || !md5.equals(module_md5)) { LocalFunID fid = new LocalFunID(module, ERT.am_undefined, arity, old_index, index, old_uniq, md5); return EFunCG.get_fun_with_handler(this.module.getName(), "badfun", 0, new EFunHandler() { @Override public EObject invoke(EProc proc, EObject[] args) throws Pausable { throw new ErlangError(am_badfun, args); } @Override public String toString() { return "#Fun<" + module + "." + old_index + "." + old_uniq + ">"; } }, this.getClass().getClassLoader()); } return maker.make(pid, freevars); } static final EBinary empty_md5 = new EBinary(new byte[16]); /** resolve a fun we got from a pre-R5 node */ public EFun resolve(final EPID pid, final int old_uniq, final int old_index, final EObject[] freevars) { EFunMaker maker = old_map.get((((long)old_index)<<32)|(long)old_uniq); if (maker==null) { LocalFunID fid = new LocalFunID(module, ERT.am_undef, 0, old_index, 0, old_uniq, empty_md5); return EFunCG.get_fun_with_handler(module.getName(), "badfun", 0, new EFunHandler() { @Override public EObject invoke(EProc proc, EObject[] args) throws Pausable { throw new ErlangError(am_badfun, args); } @Override public String toString() { return "#Fun<" + module + "." + old_index + "." + old_uniq + ">"; } }, this.getClass().getClassLoader()); } return maker.make(pid, freevars); } /** used to register a lambda/local function */ public void register(LocalFunID fun_id, EFunMaker maker) { module_md5 = fun_id.new_uniq; new_map.put(fun_id.new_index, maker); old_map.put((((long)fun_id.index) << 32) | (long)fun_id.uniq , maker); } public void bind_nif(FunID id, EFunHandler handler) throws Exception { ClassLoader loader = this.getClass().getClassLoader(); if (is_loaded()) { loader = resident.getModuleClassLoader(); } EFun fun = EFunCG.get_fun_with_handler( id.module.getName(), id.function.getName(), id.arity, handler, loader); FunctionInfo fi = get_function_info(id); fi.bind(fun); } public int trace_pattern(EAtom fun, int argi, EObject spec, TraceOpts opts) { ArrayList<FunctionInfo> fis = new ArrayList<FunctionInfo>(); for ( Map.Entry<FunID,FunctionInfo> ent : binding_points.entrySet()) { FunID fid = ent.getKey(); boolean is_fun = (fun == am__) || (fun == fid.function); boolean is_arg = argi == -1 || (argi == fid.arity); if (is_fun & is_arg) { fis.add(ent.getValue()); } } for (int i = 0; i < fis.size(); i++) { fis.get(i).trace_pattern(spec, opts); } return fis.size(); } } public static void add_import(FunID fun, FunctionBinder ref) { get_module_info(fun.module).add_import(fun, ref); } public static void add_internal(FunID fun, FunctionBinder ref) throws Exception { get_module_info(fun.module).add_internal(fun, ref); } static ModuleInfo get_module_info(EAtom module) { ModuleInfo mi; synchronized (infos) { mi = infos.get(module); if (mi == null) { infos.put(module, mi = new ModuleInfo(module)); } } return mi; } public static void add_export(EModule mod, FunID fun, EFun value) throws Exception { get_module_info(fun.module).add_export(mod, fun, value); } // static private Map<EAtom, EModule> modules = new HashMap<EAtom, // EModule>(); static void setup_module(EModule mod_inst) throws Error { ModuleInfo module_info = get_module_info(EAtom.intern(mod_inst.module_name())); module_info.setModule(mod_inst); try { mod_inst.registerImportsAndExports(); } catch (Exception e) { throw new Error(e); } module_info.warn_about_unresolved(); } public static void register_lambda(LocalFunID lambda_id, Class<? extends EFun> fun) { get_module_info(lambda_id.module).register(lambda_id, new EClassEFunMaker(fun)); } public static void bind_nif(FunID id, EFunHandler handler) throws Exception { get_module_info(id.module).bind_nif(id, handler); } /** * @param start * @return */ public static EFun resolve(FunID start) { return get_module_info(start.module).resolve(start); } public static EFun resolve(EPID pid, EAtom module, EBinary md5, int index, int old_uniq, int old_index, int arity, EObject[] freevars) { return get_module_info(module).resolve(pid, md5, index, old_uniq, old_index, arity, freevars); } public static EFun resolve(EPID pid, EAtom module, int old_uniq, int old_index, EObject[] freevars) { return get_module_info(module).resolve(pid, old_uniq, old_index, freevars); } /** * @param m * @param f * @param a * @return */ public static boolean function_exported(EAtom m, EAtom f, int a) { FunID fun = new FunID(m, f, a); return get_module_info(m).exports(fun); } /** * @param m * @return */ public static boolean module_loaded(EAtom m) { ModuleInfo mi = get_module_info(m); return mi.is_loaded(); } /** * @param mod * @return */ public static ESeq get_attributes(EAtom mod) { ModuleInfo mi = get_module_info(mod); return mi.get_attributes(); } public static ESeq get_compile(EAtom mod) { ModuleInfo mi = get_module_info(mod); return mi.get_compile(); } /** * @param mod * @return */ public static ESeq get_exports(EAtom mod) { ModuleInfo mi = get_module_info(mod); return mi.get_exports(); } public static abstract class FunctionBinder { public abstract boolean bind(EFun value); public abstract FunID getFunID(); } public static ESeq loaded_modules() { EAtom[] mods; synchronized (infos) { mods = infos.keySet().toArray(new EAtom[0]); } ESeq out = ERT.NIL; for (int i = 0; i < mods.length; i++) { out = out.cons(mods[i]); } return out; } public static EAtom delete_module(EAtom module) { ModuleInfo mi = get_module_info(module); try { return mi.unload(); } catch (ErlangException e) { throw e; } catch (Exception e) { throw new ErlangError(e); } } public static EModule get_loaded_module(EAtom module) { ModuleInfo mi = get_module_info(module); return mi.resident; } static final EAtom am__ = EAtom.intern("_"); public EModuleManager() { } public static int trace_pattern(EPID self, EAtom mod, EAtom fun, EObject arg, EObject spec, EObject opts) { int argi = (arg == am__) ? -1 : arg.testSmall().asInt(); ArrayList<ModuleInfo> mis = new ArrayList<ModuleInfo>(); if (mod == am__) { ModuleInfo mi; synchronized (infos) { mis.addAll(infos.values()); } } else { ModuleInfo mi = get_module_info(mod); mis.add(mi); } TraceOpts topts = new TraceOpts(self, spec, opts); int count = 0; for (int i = 0; i < mis.size(); i++) { ModuleInfo mi = mis.get(i); count += mi.trace_pattern(fun, argi, spec, topts); } return count; } static class TraceOpts { EMatchSpec match_spec = null; boolean is_global = true; EPID meta = null; TraceOpts(EPID self, EObject spec, EObject opts) { ESeq seq = spec.testSeq(); if (seq != null) { match_spec = EMatchSpec.compile(seq); } ESeq o = opts.testSeq(); if (o == null) return; while(!o.isNil()) { EObject v = o.head(); o = o.tail(); ETuple2 t2; if (v == am_global) { is_global = true; } else if (v == am_local) { is_global = false; } else if (v == am_meta) { meta = self; } else if ((t2=ETuple2.cast(v)) != null && t2.elem1 == am_meta) { meta = t2.elem2.testPID(); } } } } }