package erjang.beam;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import erjang.EAtom;
import erjang.EObject;
import erjang.ERT;
import erjang.FunID;
import erjang.beam.repr.Insn;
import erjang.beam.repr.Insn.IE;
import erjang.beam.repr.Insn.IL;
import erjang.beam.repr.Insn.ILI;
import erjang.beam.repr.Operands.Label;
import erjang.beam.repr.Operands.SourceOperand;
public class ModuleAnalyzer implements ModuleVisitor {
static Logger log = Logger.getLogger("erjang.beam.analyze");
Map<Label, FunInfo> info = new HashMap<Label, FunInfo>();
static class FunInfo {
Set<FunInfo> callers = new HashSet<FunInfo>();
Set<FunInfo> tail_callers = new HashSet<FunInfo>();
FunID name;
boolean may_return_tail_marker, is_pausable, call_is_pausable;
public boolean exported;
protected boolean is_called_locally_in_tail_position;
protected boolean is_anon_fun;
protected boolean is_called_locally_in_nontail_position;
protected boolean call_on_load;
boolean mustHaveFun() {
return exported
|| is_called_locally_in_tail_position
|| is_anon_fun;
}
@Override
public String toString() {
return (may_return_tail_marker ? "T" : "-") + (is_pausable ? "P" : "-")
+ " " + name;
}
void addCaller(FunInfo caller) {
callers.add(caller);
}
void addTailCaller(FunInfo caller) {
tail_callers.add(caller);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FunInfo) {
FunInfo fi = (FunInfo) obj;
return name.equals(fi.name);
}
return false;
}
}
void propagate() {
while (propagate_one())
;
}
/** */
boolean propagate_one() {
boolean effect = false;
for (FunInfo fun : info.values()) {
if (fun.is_pausable || fun.call_is_pausable) {
for (FunInfo caller : fun.callers) {
if (!caller.is_pausable) {
effect = caller.is_pausable = true;
if (log.isLoggable(Level.FINE)) {
log.fine("propagate " +fun+ " -> " + caller);
}
}
}
for (FunInfo caller : fun.tail_callers) {
if (!caller.call_is_pausable) {
effect = caller.call_is_pausable = true;
if (log.isLoggable(Level.FINE)) {
log.fine("propagate " +fun+ " -> " + caller);
}
}
}
}
}
return effect;
}
FunInfo get(Label label) {
FunInfo fi = info.get(label);
if (fi == null) {
fi = new FunInfo();
info.put(label, fi);
}
return fi;
}
FunInfo get(EAtom fun, int arity, Label label) {
FunInfo fi = get(label);
fi.name = new FunID(name, fun, arity);
return fi;
}
private EAtom name;
@Override
public void visitAttribute(EAtom att, EObject value) {
}
@Override
public void visitCompile(EAtom att, EObject value) {
}
@Override
public void visitExport(EAtom fun, int arity, int entry) {
Label label = new Label(entry);
FunInfo fi = get(label);
fi.exported = true;
}
@Override
public void visitEnd() {
propagate();
if (log.isLoggable(Level.FINE)) {
for (Map.Entry<Label, FunInfo> e : info.entrySet()) {
log.fine(e.getValue().toString());
}
}
}
@Override
public void declareFunction(EAtom fun, int arity, int startLabel) {
Label label = new Label(startLabel);
get(fun,arity,label);
}
@Override
public FunctionVisitor visitFunction(EAtom name, int arity,
final int startLabel) {
if (log.isLoggable(Level.FINE)) {
log.fine("== analyzing "+ModuleAnalyzer.this.name+":"+name+"/"+arity);
}
Label start = new Label(startLabel);
final FunInfo self = get(name, arity, start);
return new FunctionVisitor() {
@Override
public BlockVisitor visitLabeledBlock(int label) {
return new BlockVisitor() {
@Override
public void visitInsn(Insn insn) {
BeamOpcode op;
switch (op = insn.opcode()) {
case on_load:
self.call_on_load = true;
break;
case send: {
if (log.isLoggable(Level.FINE) && !self.is_pausable) {
log.fine("pausable: send");
}
self.is_pausable = true;
break;
}
case call: {
Insn.IL cl = (Insn.IL) insn;
FunInfo target = get(cl.label);
BuiltInFunction bif =
BIFUtil.getMethod(target.name.module,
target.name.function, target.name.arity, false, false);
if (bif == null) {
target.addCaller(self);
target.is_called_locally_in_nontail_position = true;
}
break;
}
case call_last: {
ILI cl = (Insn.ILI) insn;
boolean is_self_call = cl.label.nr == startLabel;
self.may_return_tail_marker |= !is_self_call;
FunInfo target = get(cl.label);
BuiltInFunction bif =
BIFUtil.getMethod(target.name.module,
target.name.function, target.name.arity, false, false);
if (bif == null) {
target.addTailCaller(self);
target.is_called_locally_in_tail_position |= !is_self_call;
}
break;
}
case call_only: {
IL cl = (Insn.IL) insn;
boolean is_self_call = cl.label.nr == startLabel;
self.may_return_tail_marker |= !is_self_call;
FunInfo target = get(cl.label);
BuiltInFunction bif =
BIFUtil.getMethod(target.name.module,
target.name.function, target.name.arity, false, false);
if (bif == null) {
target.addTailCaller(self);
target.is_called_locally_in_tail_position |= !is_self_call;
}
break;
}
case make_fun2: {
Insn.F fi = (Insn.F) insn;
Label anon = new Label(fi.anon_fun.label);
get(anon).is_anon_fun = true;
break;
}
case apply_last:
case i_call_fun_last: {
if (log.isLoggable(Level.FINE) && !self.call_is_pausable) {
log.fine("call_pausable: " + op);
}
self.may_return_tail_marker = true;
self.call_is_pausable = true;
break;
}
case call_ext_last:
case call_ext_only:
{
Insn.IE e = (IE) insn;
String mod = e.ext_fun.mod.getName();
String fun = e.ext_fun.fun.getName();
int arity = e.ext_fun.arity;
if (mod.equals("erlang")
&& arity == 1
&& (fun.equals("throw") || fun.equals("error") || fun.equals("exit") || fun.equals("nif_error"))) {
break;
}
}
{
if (log.isLoggable(Level.FINE) && !self.call_is_pausable) {
log.fine("call_pausable: " + op);
}
Insn.IE e = (IE) insn;
String mod = e.ext_fun.mod.getName();
String fun = e.ext_fun.fun.getName();
int arity = e.ext_fun.arity;
BuiltInFunction bif = BIFUtil.getMethod(mod,
fun, arity, false, false);
if (bif != null) {
if (log.isLoggable(Level.FINE) && !self.is_pausable && bif.isPausable()) {
log.fine("pausable: calls " + bif.javaMethod);
}
self.is_pausable |= bif.isPausable();
break;
}
self.may_return_tail_marker = true;
self.call_is_pausable = true;
/* FALL THRU */
}
case call_ext:
if (self.is_pausable) {
break;
}
Insn.IE e = (IE) insn;
String mod = e.ext_fun.mod.getName();
String fun = e.ext_fun.fun.getName();
int arity = e.ext_fun.arity;
BuiltInFunction bif = BIFUtil.getMethod(mod,
fun, arity, false, false);
if (bif != null) {
if (log.isLoggable(Level.FINE) && !self.is_pausable && bif.isPausable()) {
log.fine("pausable: calls " + bif.javaMethod);
}
self.is_pausable |= bif.isPausable();
} else if (op == BeamOpcode.call_ext) {
if (log.isLoggable(Level.FINE) && !self.is_pausable) {
log.fine("pausable: calls "+mod+":"+fun+"/"+arity);
}
self.is_pausable = true;
}
break;
case apply:
case call_fun:
case wait:
case wait_timeout:
if (log.isLoggable(Level.FINE) && !self.is_pausable) {
log.fine("pausable: "+op);
}
self.is_pausable = true;
break;
case bif:
case bif0:
case bif1:
case bif2: {
// no reason to go through this pain, if we're
// already pausable
if (self.is_pausable)
break;
Insn.Bif bi = (Insn.Bif) insn;
EAtom name = bi.ext_fun.fun;
SourceOperand[] srcs = bi.args;
bif = BIFUtil.getMethod("erlang",
name.getName(), srcs.length,
false, true);
if (bif == null) {
throw new Error("missing bif: "+bi.ext_fun);
}
self.is_pausable |= bif.isPausable();
if (log.isLoggable(Level.FINE) && self.is_pausable) {
log.fine("pausable: calls "+bif.javaMethod);
}
}
default:
break;
}
}
@Override
public void visitEnd() {
}
};
}
@Override
public void visitEnd() {
}
};
}
@Override
public void visitModule(EAtom name) {
this.name = name;
}
public Map<FunID,FunInfo> getFunInfos() {
Map<FunID, FunInfo> res = new HashMap<FunID, FunInfo>();
for (FunInfo fi : info.values()) {
res.put(fi.name, fi);
}
return res;
}
}