use std::collections::HashMap;
use std::fmt;
use std::fmt::{Display, Formatter};
pub use petgraph;
use petgraph::graphmap::DiGraphMap;
use petgraph::visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef};
use crate::types::{FunPtr, Function, RefFun};
use crate::{Bytecode, Opcode, Resolve, Type};
pub enum Call {
Direct,
Closure,
}
pub type Callgraph = DiGraphMap<RefFun, Call>;
type RegCtx = HashMap<usize, RefFun>;
pub fn find_calls<'a>(
code: &'a Bytecode,
f: &'a Function,
reg_ctx: &'a RegCtx,
) -> impl Iterator<Item = (Call, RefFun, RegCtx)> + 'a {
macro_rules! build_ctx {
($i:ident; $args:expr) => {{
let mut tmp = RegCtx::new();
for (p, arg) in $args.into_iter().enumerate() {
if matches!(code[f[*arg]], Type::Fun(_)) {
if let Some(value) = f
.find_last_closure_assign(code, *arg, $i)
.or_else(|| reg_ctx.get(&(arg.0 as usize)).copied())
{
tmp.insert(p, value);
}
}
}
tmp
}};
}
f.ops.iter().enumerate().filter_map(move |(i, o)| match o {
Opcode::Call0 { fun, .. } => Some((Call::Direct, *fun, RegCtx::new())),
Opcode::Call1 { fun, arg0, .. } => Some((Call::Direct, *fun, build_ctx!(i; [arg0]))),
Opcode::Call2 {
fun, arg0, arg1, ..
} => Some((Call::Direct, *fun, build_ctx!(i; [arg0, arg1]))),
Opcode::Call3 {
fun,
arg0,
arg1,
arg2,
..
} => Some((Call::Direct, *fun, build_ctx!(i; [arg0, arg1, arg2]))),
Opcode::Call4 {
fun,
arg0,
arg1,
arg2,
arg3,
..
} => Some((Call::Direct, *fun, build_ctx!(i; [arg0, arg1, arg2, arg3]))),
Opcode::CallN { fun, args, .. } => Some((Call::Direct, *fun, build_ctx!(i; args))),
Opcode::CallClosure { fun, args, .. } => f
.find_last_closure_assign(code, *fun, i)
.map(|f| (Call::Closure, f, build_ctx!(i; args)))
.or_else(|| {
reg_ctx
.get(&(fun.0 as usize))
.map(|param| (Call::Closure, *param, build_ctx!(i; args)))
}),
Opcode::CallMethod { field, args, .. } => code[f[args[0]]]
.get_type_obj()
.map(|o| (Call::Direct, o.protos[field.0].findex, build_ctx!(i; args))),
Opcode::CallThis { field, args, .. } => code[f.regs[0]]
.get_type_obj()
.map(|o| (Call::Direct, o.protos[field.0].findex, build_ctx!(i; args))),
_ => None,
})
}
pub fn call_graph(code: &Bytecode, f: RefFun, max_depth: usize) -> Callgraph {
let mut g = Callgraph::new();
match code.get(f) {
FunPtr::Fun(f) => {
g.add_node(f.findex);
build_graph_rec(code, &mut g, f, &RegCtx::new(), max_depth);
}
FunPtr::Native(n) => {
g.add_node(n.findex);
}
}
g
}
fn build_graph_rec(code: &Bytecode, g: &mut Callgraph, f: &Function, ctx: &RegCtx, depth: usize) {
if depth == 0 {
return;
}
for (call, fun, ctx) in find_calls(code, f, ctx) {
if fun.is_from_std(code) {
match code.get(fun) {
FunPtr::Fun(fun) => {
if !g.contains_node(fun.findex) {
g.add_node(fun.findex);
build_graph_rec(code, g, fun, &ctx, depth - 1);
}
g.add_edge(f.findex, fun.findex, call);
}
FunPtr::Native(n) => {
if !g.contains_node(n.findex) {
g.add_node(n.findex);
}
g.add_edge(f.findex, n.findex, call);
}
}
}
}
}
static TYPE: [&str; 2] = ["graph", "digraph"];
static EDGE: [&str; 2] = ["--", "->"];
static INDENT: &str = " ";
pub struct GraphDisplay<'a> {
g: &'a Callgraph,
code: &'a Bytecode,
}
impl Display for GraphDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "{} {{", TYPE[self.g.is_directed() as usize])?;
writeln!(f, "{}fontname=\"Helvetica,Arial,sans-serif\"", INDENT)?;
writeln!(
f,
"{}node [fontname=\"Helvetica,Arial,sans-serif\" style=filled fillcolor=\"#f8f8f8\"]",
INDENT
)?;
writeln!(
f,
"{}edge [fontname=\"Helvetica,Arial,sans-serif\"]",
INDENT
)?;
for node in self.g.node_references() {
writeln!(
f,
"{}{} [ label = \"{:?}\" fontsize=18 shape=box color=\"#b20400\" fillcolor=\"#edd6d5\" ]",
INDENT,
self.g.to_index(node.id()),
self.code.get(*node.weight())
)?;
}
for edge in self.g.edge_references() {
writeln!(
f,
"{}{} {} {} [ label = \"{}\" ]",
INDENT,
self.g.to_index(edge.source()),
EDGE[self.g.is_directed() as usize],
self.g.to_index(edge.target()),
match edge.weight() {
Call::Direct => "",
Call::Closure => "closure",
}
)?;
}
writeln!(f, "}}")?;
Ok(())
}
}
pub fn display_graph<'a>(g: &'a Callgraph, code: &'a Bytecode) -> GraphDisplay<'a> {
GraphDisplay { g, code }
}