diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index ca82b2b3aea..a61c59fe553 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -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 +#include + namespace wasm { namespace { @@ -360,10 +364,86 @@ struct DiscardGlobalEffects : public Pass { } }; +struct GenerateCallGraph : public Pass { + bool modifiesBinaryenIR() override { return false; } + + void run(Module* module) override { + std::map funcInfos = + analyzeFuncs(*module, getPassOptions()); + + auto callGraph = + buildCallGraph(*module, funcInfos, getPassOptions().closedWorld); + + std::map nodeTypes; + std::map> 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 diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index dc6d91feb4e..190b2486f78 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -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( diff --git a/src/passes/passes.h b/src/passes/passes.h index 2fdacd84ab0..2406a6e9a62 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -59,6 +59,7 @@ Pass* createFunctionMetricsPass(); Pass* createGenerateDynCallsPass(); Pass* createGenerateI64DynCallsPass(); Pass* createGenerateGlobalEffectsPass(); +Pass* createGenerateCallGraphPass(); Pass* createGlobalRefiningPass(); Pass* createGlobalStructInferencePass(); Pass* createGlobalStructInferenceDescCastPass(); diff --git a/src/support/utilities.h b/src/support/utilities.h index 3f40111c451..272488e18f8 100644 --- a/src/support/utilities.h +++ b/src/support/utilities.h @@ -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 struct overloaded : Ts... { + using Ts::operator()...; +}; + } // namespace wasm #endif // wasm_support_utilities_h diff --git a/test/lit/passes/generate-call-graph.wast b/test/lit/passes/generate-call-graph.wast new file mode 100644 index 00000000000..dd34e4be47f --- /dev/null +++ b/test/lit/passes/generate-call-graph.wast @@ -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: }