Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
#include "pass.h"
#include "support/graph_traversal.h"
#include "support/strongly_connected_components.h"
#include "support/utilities.h"
#include "wasm.h"

#include <iostream>
#include <sstream>

namespace wasm {

namespace {
Expand Down Expand Up @@ -360,10 +364,86 @@ struct DiscardGlobalEffects : public Pass {
}
};

struct GenerateCallGraph : public Pass {
bool modifiesBinaryenIR() override { return false; }

void run(Module* module) override {
std::map<Function*, FuncInfo> funcInfos =
analyzeFuncs(*module, getPassOptions());

auto callGraph =
buildCallGraph(*module, funcInfos, getPassOptions().closedWorld);

std::map<std::string, std::string> nodeTypes;
std::map<std::string, std::map<std::string, std::string>> sortedGraph;

auto getNodeType = [](const CallGraphNode& node) {
return std::visit(overloaded{[](Function*) { return "function"; },
[](HeapType) { return "type"; }},
node);
};

for (const auto& [caller, callees] : callGraph) {
std::string callerName = nodeToString(caller);
nodeTypes[callerName] = getNodeType(caller);

for (const auto& callee : callees) {
std::string calleeName = nodeToString(callee);
nodeTypes[calleeName] = getNodeType(callee);

std::string style = std::visit(
overloaded{
[](Function*, Function*) {
return " [style=\"solid\", color=\"black\", kind=\"direct\"]";
},
[](Function*, HeapType) {
return " [style=\"dotted\", color=\"black\", kind=\"indirect\"]";
},
[](HeapType, HeapType) {
return " [style=\"solid\", color=\"blue\", kind=\"subtyping\"]";
},
[](HeapType, Function*) {
return " [style=\"dashed\", color=\"green\", "
"kind=\"implementation\"]";
}},
caller,
callee);

sortedGraph[callerName][calleeName] = style;
}
}

std::cout << "digraph CallGraph {\n";
for (const auto& [nodeName, nodeType] : nodeTypes) {
std::cout << " \"" << nodeName << "\" [kind=\"" << nodeType << "\"];\n";
}
for (const auto& [callerName, callees] : sortedGraph) {
for (const auto& [calleeName, style] : callees) {
std::cout << " \"" << callerName << "\" -> \"" << calleeName << "\""
<< style << ";\n";
}
}
std::cout << "}\n";
}

std::string nodeToString(const CallGraphNode& node) {
return std::visit(
overloaded{[](Function* func) { return func->name.toString(); },
[](HeapType type) {
std::stringstream ss;
ss << type;
return ss.str();
}},
node);
}
};

} // namespace

Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); }

Pass* createDiscardGlobalEffectsPass() { return new DiscardGlobalEffects(); }

Pass* createGenerateCallGraphPass() { return new GenerateCallGraph(); }

} // namespace wasm
2 changes: 2 additions & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ void PassRegistry::registerPasses() {
registerPass("generate-global-effects",
"generate global effect info (helps later passes)",
createGenerateGlobalEffectsPass);
registerPass(
"generate-call-graph", "print call graph", createGenerateCallGraphPass);
registerPass(
"global-refining", "refine the types of globals", createGlobalRefiningPass);
registerPass(
Expand Down
1 change: 1 addition & 0 deletions src/passes/passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Pass* createFunctionMetricsPass();
Pass* createGenerateDynCallsPass();
Pass* createGenerateI64DynCallsPass();
Pass* createGenerateGlobalEffectsPass();
Pass* createGenerateCallGraphPass();
Pass* createGlobalRefiningPass();
Pass* createGlobalStructInferencePass();
Pass* createGlobalStructInferenceDescCastPass();
Expand Down
11 changes: 11 additions & 0 deletions src/support/utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ class Fatal {
#define WASM_UNREACHABLE(msg) wasm::handle_unreachable()
#endif

// Helper to create an invocable with an overloaded operator(), for use with
// std::visit e.g.
// std::visit(
// overloaded{
// [](const A& a) { ... },
// [](const B& b) { ... }},
// variant)
template<class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};

} // namespace wasm

#endif // wasm_support_utilities_h
39 changes: 39 additions & 0 deletions test/lit/passes/generate-call-graph.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
;; RUN: foreach %s %t wasm-opt --generate-call-graph | filecheck %s
;; RUN: foreach %s %t wasm-opt --generate-call-graph --closed-world | filecheck %s --check-prefix CLOSED

(module
(type $sig (func))
(table 1 1 funcref)
(elem (i32.const 0) $D)

(func $A
(call $B)
(call_indirect (type $sig) (i32.const 0))
)
(func $B (call $C))
(func $C)
(func $D (type $sig))
)
;; CHECK: digraph CallGraph {
;; CHECK-NEXT: "A" [kind="function"];
;; CHECK-NEXT: "B" [kind="function"];
;; CHECK-NEXT: "C" [kind="function"];
;; CHECK-NEXT: "D" [kind="function"];
;; CHECK-NEXT: "A" -> "B" [style="solid", color="black", kind="direct"];
;; CHECK-NEXT: "B" -> "C" [style="solid", color="black", kind="direct"];
;; CHECK-NEXT: }

;; CLOSED: digraph CallGraph {
;; CLOSED-NEXT: "(type $func.0 (func))" [kind="type"];
;; CLOSED-NEXT: "A" [kind="function"];
;; CLOSED-NEXT: "B" [kind="function"];
;; CLOSED-NEXT: "C" [kind="function"];
;; CLOSED-NEXT: "D" [kind="function"];
;; CLOSED-NEXT: "(type $func.0 (func))" -> "A" [style="dashed", color="green", kind="implementation"];
;; CLOSED-NEXT: "(type $func.0 (func))" -> "B" [style="dashed", color="green", kind="implementation"];
;; CLOSED-NEXT: "(type $func.0 (func))" -> "C" [style="dashed", color="green", kind="implementation"];
;; CLOSED-NEXT: "(type $func.0 (func))" -> "D" [style="dashed", color="green", kind="implementation"];
;; CLOSED-NEXT: "A" -> "(type $func.0 (func))" [style="dotted", color="black", kind="indirect"];
;; CLOSED-NEXT: "A" -> "B" [style="solid", color="black", kind="direct"];
;; CLOSED-NEXT: "B" -> "C" [style="solid", color="black", kind="direct"];
;; CLOSED-NEXT: }
Loading