Coverage Report

Created: 2025-06-24 07:59

/src/solidity/libyul/optimiser/FunctionSpecializer.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/optimiser/FunctionSpecializer.h>
20
21
#include <libyul/optimiser/ASTCopier.h>
22
#include <libyul/optimiser/CallGraphGenerator.h>
23
#include <libyul/optimiser/NameCollector.h>
24
#include <libyul/optimiser/NameDispenser.h>
25
26
#include <libyul/AST.h>
27
#include <libyul/YulName.h>
28
#include <libsolutil/CommonData.h>
29
30
#include <range/v3/algorithm/any_of.hpp>
31
#include <range/v3/view/enumerate.hpp>
32
33
#include <variant>
34
35
using namespace solidity::util;
36
using namespace solidity::yul;
37
38
FunctionSpecializer::LiteralArguments FunctionSpecializer::specializableArguments(
39
  FunctionCall const& _f
40
)
41
339k
{
42
339k
  auto heuristic = [&](Expression const& _e) -> std::optional<Expression>
43
408k
  {
44
408k
    if (std::holds_alternative<Literal>(_e))
45
284k
      return ASTCopier{}.translate(_e);
46
123k
    return std::nullopt;
47
408k
  };
48
49
339k
  return applyMap(_f.arguments, heuristic);
50
339k
}
51
52
void FunctionSpecializer::operator()(FunctionCall& _f)
53
3.13M
{
54
3.13M
  ASTModifier::operator()(_f);
55
56
  // TODO When backtracking is implemented, the restriction of recursive functions can be lifted.
57
3.13M
  if (
58
3.13M
    isBuiltinFunctionCall(_f) ||
59
3.13M
    (std::holds_alternative<Identifier>(_f.functionName) && m_recursiveFunctions.count(std::get<Identifier>(_f.functionName).name))
60
3.13M
  )
61
2.79M
    return;
62
339k
  yulAssert(std::holds_alternative<Identifier>(_f.functionName));
63
339k
  auto& identifier = std::get<Identifier>(_f.functionName);
64
65
339k
  LiteralArguments arguments = specializableArguments(_f);
66
67
368k
  if (ranges::any_of(arguments, [](auto& _a) { return _a.has_value(); }))
68
257k
  {
69
257k
    YulName oldName = std::move(identifier.name);
70
257k
    auto newName = m_nameDispenser.newName(oldName);
71
72
257k
    m_oldToNewMap[oldName].emplace_back(std::make_pair(newName, arguments));
73
74
257k
    identifier.name = newName;
75
257k
    _f.arguments = util::filter(
76
257k
      _f.arguments,
77
323k
      applyMap(arguments, [](auto& _a) { return !_a; })
78
257k
    );
79
257k
  }
80
339k
}
81
82
FunctionDefinition FunctionSpecializer::specialize(
83
  FunctionDefinition const& _f,
84
  YulName _newName,
85
  FunctionSpecializer::LiteralArguments _arguments
86
)
87
257k
{
88
257k
  yulAssert(_arguments.size() == _f.parameters.size(), "");
89
90
257k
  std::map<YulName, YulName> translatedNames = applyMap(
91
257k
    NameCollector{_f, NameCollector::OnlyVariables}.names(),
92
257k
    [&](auto& _name) -> std::pair<YulName, YulName>
93
7.34M
    {
94
7.34M
      return std::make_pair(_name, m_nameDispenser.newName(_name));
95
7.34M
    },
96
257k
    std::map<YulName, YulName>{}
97
257k
  );
98
99
257k
  FunctionDefinition newFunction = std::get<FunctionDefinition>(FunctionCopier{translatedNames}(_f));
100
101
  // Function parameters that will be specialized inside the body are converted into variable
102
  // declarations.
103
257k
  std::vector<Statement> missingVariableDeclarations;
104
257k
  for (auto&& [index, argument]: _arguments | ranges::views::enumerate)
105
323k
    if (argument)
106
284k
      missingVariableDeclarations.emplace_back(
107
284k
        VariableDeclaration{
108
284k
          _f.debugData,
109
284k
          std::vector<NameWithDebugData>{newFunction.parameters[index]},
110
284k
          std::make_unique<Expression>(std::move(*argument))
111
284k
        }
112
284k
      );
113
114
257k
  newFunction.body.statements =
115
257k
    std::move(missingVariableDeclarations) + std::move(newFunction.body.statements);
116
117
  // Only take those indices that cannot be specialized, i.e., whose value is `nullopt`.
118
257k
  newFunction.parameters =
119
257k
    util::filter(
120
257k
      newFunction.parameters,
121
323k
      applyMap(_arguments, [&](auto const& _v) { return !_v; })
122
257k
    );
123
124
257k
  newFunction.name = std::move(_newName);
125
126
257k
  return newFunction;
127
257k
}
128
129
void FunctionSpecializer::run(OptimiserStepContext& _context, Block& _ast)
130
77.5k
{
131
77.5k
  FunctionSpecializer f{
132
77.5k
    CallGraphGenerator::callGraph(_ast).recursiveFunctions(),
133
77.5k
    _context.dispenser
134
77.5k
  };
135
77.5k
  f(_ast);
136
137
77.5k
  iterateReplacing(_ast.statements, [&](Statement& _s) -> std::optional<std::vector<Statement>>
138
277k
  {
139
277k
    if (std::holds_alternative<FunctionDefinition>(_s))
140
199k
    {
141
199k
      auto& functionDefinition = std::get<FunctionDefinition>(_s);
142
143
199k
      if (f.m_oldToNewMap.count(functionDefinition.name))
144
19.3k
      {
145
19.3k
        std::vector<Statement> out = applyMap(
146
19.3k
          f.m_oldToNewMap.at(functionDefinition.name),
147
19.3k
          [&](auto& _p) -> Statement
148
257k
          {
149
257k
            return f.specialize(functionDefinition, std::move(_p.first), std::move(_p.second));
150
257k
          }
151
19.3k
        );
152
19.3k
        return std::move(out) + make_vector<Statement>(std::move(functionDefinition));
153
19.3k
      }
154
199k
    }
155
156
258k
    return std::nullopt;
157
277k
  });
158
77.5k
}