/src/solidity/libyul/backends/evm/ssa/SSACFG.cpp
Line | Count | Source |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | |
19 | | #include <libyul/backends/evm/ssa/SSACFG.h> |
20 | | |
21 | | #include <libyul/backends/evm/ssa/ControlFlowGraphs.h> |
22 | | #include <libyul/backends/evm/ssa/JunkAdmittingBlocksFinder.h> |
23 | | #include <libyul/backends/evm/ssa/LivenessAnalysis.h> |
24 | | #include <libyul/backends/evm/ssa/io/DotExporterBase.h> |
25 | | |
26 | | #include <libsolutil/StringUtils.h> |
27 | | |
28 | | #include <fmt/ranges.h> |
29 | | |
30 | | #include <range/v3/view/iota.hpp> |
31 | | #include <range/v3/view/transform.hpp> |
32 | | #include <range/v3/view/zip.hpp> |
33 | | |
34 | | using namespace solidity; |
35 | | using namespace solidity::util; |
36 | | using namespace solidity::yul; |
37 | | using namespace solidity::yul::ssa; |
38 | | |
39 | | namespace |
40 | | { |
41 | | |
42 | | /// Build a human-readable Phi/Upsilon annotation for a phi value. |
43 | | /// Shows which upsilons feed it, listed per predecessor block. |
44 | | std::string formatPhi(SSACFG const& _cfg, InstId _phiId) |
45 | 0 | { |
46 | | // Collect all upsilons targeting _phiId from the whole CFG. |
47 | 0 | std::vector<std::string> formattedUpsilons; |
48 | 0 | for (BlockId const blockId: _cfg.liveBlocks()) |
49 | 0 | _cfg.forEachUpsilon(_cfg.block(blockId), [&](InstId const instId, SSACFG::Inst const& inst) { |
50 | 0 | if (_cfg.upsilonPhi(instId) == _phiId) |
51 | 0 | formattedUpsilons.push_back( |
52 | 0 | fmt::format("Block {} => {}", blockId.value, inst.inputs.at(0).str(_cfg)) |
53 | 0 | ); |
54 | 0 | }); |
55 | 0 | if (!formattedUpsilons.empty()) |
56 | 0 | return fmt::format("φ(\\l\\\n\t{}\\l\\\n)", fmt::join(formattedUpsilons, ",\\l\\\n\t")); |
57 | 0 | return "φ()"; |
58 | 0 | } |
59 | | |
60 | | class SSACFGDotExporter: public io::DotExporterBase |
61 | | { |
62 | | public: |
63 | | SSACFGDotExporter(SSACFG const& _cfg, size_t _functionIndex, LivenessAnalysis const* _liveness, ControlFlowGraphs const* _controlFlow): |
64 | 0 | DotExporterBase(_cfg, _functionIndex), |
65 | 0 | m_liveness(_liveness), |
66 | 0 | m_controlFlow(_controlFlow) |
67 | 0 | { |
68 | 0 | if (_liveness) |
69 | 0 | m_junkAdmittingBlocks = std::make_unique<JunkAdmittingBlocksFinder>(_cfg, _liveness->topologicalSort()); |
70 | 0 | } |
71 | | |
72 | | protected: |
73 | | void writeBlockLabel(std::ostream& _out, BlockId _blockId) override |
74 | 0 | { |
75 | 0 | auto const& block = m_cfg.block(_blockId); |
76 | 0 | auto const valueToString = [&](InstId const& valueId) { return valueId.str(m_cfg); }; |
77 | |
|
78 | 0 | if (m_liveness) |
79 | 0 | { |
80 | 0 | _out << fmt::format( |
81 | 0 | "\\\nBlock {}; ({}, max {})\\n", |
82 | 0 | _blockId.value, |
83 | 0 | m_liveness->topologicalSort().preOrderIndexOf(_blockId.value), |
84 | 0 | m_liveness->topologicalSort().maxSubtreePreOrderIndexOf(_blockId.value) |
85 | 0 | ); |
86 | 0 | _out << fmt::format( |
87 | 0 | "LiveIn: {}\\l\\\n", |
88 | 0 | fmt::join(m_liveness->liveIn(_blockId) | ranges::views::transform([&](auto const& liveIn) { return valueToString(liveIn.first) + fmt::format("[{}]", liveIn.second); }), ", ") |
89 | 0 | ); |
90 | 0 | _out << fmt::format( |
91 | 0 | "LiveOut: {}\\l\\n", |
92 | 0 | fmt::join(m_liveness->liveOut(_blockId) | ranges::views::transform([&](auto const& liveOut) { return valueToString(liveOut.first) + fmt::format("[{}]", liveOut.second); }), ", ") |
93 | 0 | ); |
94 | 0 | auto const usedVariables = m_liveness->used(_blockId); |
95 | 0 | _out << fmt::format( |
96 | 0 | "Used: {}\\l\\n", |
97 | 0 | fmt::join(usedVariables | ranges::views::transform([&](auto const& used) { return valueToString(used.first) + fmt::format("[{}]", used.second); }), ", ") |
98 | 0 | ); |
99 | 0 | } |
100 | 0 | else |
101 | 0 | _out << fmt::format("\\\nBlock {}\\n", _blockId.value); |
102 | | |
103 | | // Phis first, then BuiltinCall / Call in program order. Upsilons are rendered |
104 | | // under successor phis (via formatPhi) and skipped here. Trailing Projections of |
105 | | // multi-return calls are rendered as standalone lines following their producer. |
106 | 0 | m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) { |
107 | 0 | _out << fmt::format("phi{} := {}\\l\\\n", instId.value, formatPhi(m_cfg, instId)); |
108 | 0 | }); |
109 | 0 | m_cfg.forEachOperation(block, [&](InstId const instId, SSACFG::Inst const& inst) { |
110 | 0 | std::string label; |
111 | 0 | switch (inst.opcode) |
112 | 0 | { |
113 | 0 | case InstOpcode::Call: |
114 | 0 | { |
115 | 0 | auto const graphID = m_cfg.callPayload(instId).graphID; |
116 | 0 | label = m_controlFlow ? m_controlFlow->functionGraph(graphID)->name : fmt::format("func{}", graphID); |
117 | 0 | break; |
118 | 0 | } |
119 | 0 | case InstOpcode::BuiltinCall: |
120 | 0 | label = m_cfg.evmDialect.builtin(m_cfg.builtinPayload(instId).builtin).name; |
121 | 0 | break; |
122 | 0 | case InstOpcode::MemoryGuard: |
123 | 0 | if (m_controlFlow && m_controlFlow->memoryGuard) |
124 | 0 | label = fmt::format("memoryguard<{}>", toCompactHexWithPrefix(*m_controlFlow->memoryGuard)); |
125 | 0 | else |
126 | 0 | label = "memoryguard"; |
127 | 0 | break; |
128 | 0 | default: |
129 | 0 | yulAssert(false); |
130 | 0 | } |
131 | 0 | if (m_cfg.numReturnsOf(instId) >= 1) |
132 | 0 | _out << fmt::format("{} := ", valueToString(instId)); |
133 | 0 | _out << fmt::format( |
134 | 0 | "{}({})\\l\\\n", |
135 | 0 | escapeLabel(label), |
136 | 0 | fmt::join(inst.inputs | ranges::views::transform(valueToString), ", ") |
137 | 0 | ); |
138 | 0 | for (InstId const projId: m_cfg.projectionsOf(instId)) |
139 | 0 | _out << fmt::format( |
140 | 0 | "{} := {}.proj({})\\l\\\n", |
141 | 0 | valueToString(projId), |
142 | 0 | valueToString(instId), |
143 | 0 | static_cast<unsigned>(m_cfg.projectionIndex(projId)) |
144 | 0 | ); |
145 | 0 | }); |
146 | 0 | } |
147 | | |
148 | | std::vector<std::pair<std::string, std::string>> blockNodeAttributes(BlockId _blockId) override |
149 | 0 | { |
150 | 0 | if (m_junkAdmittingBlocks && m_junkAdmittingBlocks->allowsAdditionOfJunk(_blockId)) |
151 | 0 | return {{"fillcolor", "\"#FF746C\""}, {"style", "filled"}}; |
152 | 0 | return {}; |
153 | 0 | } |
154 | | |
155 | | EdgeStyle edgeStyle(BlockId _source, BlockId _target) override |
156 | 0 | { |
157 | 0 | if (m_liveness && m_liveness->topologicalSort().backEdge(_source, _target)) |
158 | 0 | return EdgeStyle::Dashed; |
159 | 0 | return EdgeStyle::Solid; |
160 | 0 | } |
161 | | |
162 | | private: |
163 | | LivenessAnalysis const* m_liveness; |
164 | | ControlFlowGraphs const* m_controlFlow; |
165 | | std::unique_ptr<JunkAdmittingBlocksFinder> m_junkAdmittingBlocks; |
166 | | }; |
167 | | |
168 | | } |
169 | | |
170 | | std::string SSACFG::toDot( |
171 | | bool _includeDiGraphDefinition, |
172 | | std::optional<size_t> _functionIndex, |
173 | | LivenessAnalysis const* _liveness, |
174 | | ControlFlowGraphs const* _controlFlow |
175 | | ) const |
176 | 0 | { |
177 | 0 | SSACFGDotExporter exporter(*this, _functionIndex.value_or(isMainGraph() ? 0 : 1), _liveness, _controlFlow); |
178 | 0 | if (!isMainGraph()) |
179 | 0 | return exporter.exportFunction(*this, _includeDiGraphDefinition); |
180 | 0 | else |
181 | 0 | return exporter.exportBlocks(entry, _includeDiGraphDefinition); |
182 | 0 | } |