mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
145 lines
4.7 KiB
C++
145 lines
4.7 KiB
C++
/*
|
|
This file is part of solidity.
|
|
|
|
solidity is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
solidity is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
|
|
#pragma once
|
|
|
|
#include <tools/yulPhaser/Program.h>
|
|
|
|
#include <libyul/optimiser/Metrics.h>
|
|
|
|
#include <cstddef>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
namespace solidity::phaser
|
|
{
|
|
|
|
/**
|
|
* Structure used by @a ProgramCache to store intermediate programs and metadata associated
|
|
* with them.
|
|
*/
|
|
struct CacheEntry
|
|
{
|
|
Program program;
|
|
size_t roundNumber;
|
|
|
|
CacheEntry(Program _program, size_t _roundNumber):
|
|
program(std::move(_program)),
|
|
roundNumber(_roundNumber) {}
|
|
};
|
|
|
|
/**
|
|
* Stores statistics about current cache usage.
|
|
*/
|
|
struct CacheStats
|
|
{
|
|
/// Weights used to compute totalCodeSize.
|
|
/// The goal here is to get a result proportional to the amount of memory taken by the AST.
|
|
/// Each statement/expression gets 1 just for existing. We add more if it contains any extra
|
|
/// data that won't be visited separately by ASTWalker.
|
|
static yul::CodeWeights constexpr StorageWeights = {
|
|
/* expressionStatementCost = */ 1,
|
|
/* assignmentCost = */ 1,
|
|
/* variableDeclarationCost = */ 1,
|
|
/* functionDefinitionCost = */ 1,
|
|
/* ifCost = */ 1,
|
|
/* switchCost = */ 1,
|
|
/* caseCost = */ 1,
|
|
/* forLoopCost = */ 1,
|
|
/* breakCost = */ 1,
|
|
/* continueCost = */ 1,
|
|
/* leaveCost = */ 1,
|
|
/* blockCost = */ 1,
|
|
|
|
/* functionCallCost = */ 1,
|
|
/* identifierCost = */ 1,
|
|
/* literalCost = */ 1,
|
|
};
|
|
|
|
size_t hits;
|
|
size_t misses;
|
|
size_t totalCodeSize;
|
|
std::map<size_t, size_t> roundEntryCounts;
|
|
|
|
CacheStats& operator+=(CacheStats const& _other);
|
|
CacheStats operator+(CacheStats const& _other) const { return CacheStats(*this) += _other; }
|
|
|
|
bool operator==(CacheStats const& _other) const;
|
|
bool operator!=(CacheStats const& _other) const { return !(*this == _other); }
|
|
};
|
|
|
|
/**
|
|
* Class that optimises programs one step at a time which allows it to store and later reuse the
|
|
* results of the intermediate steps.
|
|
*
|
|
* The cache keeps track of the current round number and associates newly created entries with it.
|
|
* @a startRound() must be called at the beginning of a round so that entries that are too old
|
|
* can be purged. The current strategy is to store programs corresponding to all possible prefixes
|
|
* encountered in the current and the previous rounds. Entries older than that get removed to
|
|
* conserve memory.
|
|
*
|
|
* @a gatherStats() allows getting statistics useful for determining cache effectiveness.
|
|
*
|
|
* The current strategy does speed things up (about 4:1 hit:miss ratio observed in my limited
|
|
* experiments) but there's room for improvement. We could fit more useful programs in
|
|
* the cache by being more picky about which ones we choose.
|
|
*
|
|
* There is currently no way to purge entries without starting a new round. Since the programs
|
|
* take a lot of memory, this may lead to the cache eating up all the available RAM if sequences are
|
|
* long and programs large. A limiter based on entry count or total program size would be useful.
|
|
*/
|
|
class ProgramCache
|
|
{
|
|
public:
|
|
explicit ProgramCache(Program _program):
|
|
m_program(std::move(_program)) {}
|
|
|
|
Program optimiseProgram(
|
|
std::string const& _abbreviatedOptimisationSteps,
|
|
size_t _repetitionCount = 1
|
|
);
|
|
void startRound(size_t _nextRoundNumber);
|
|
void clear();
|
|
|
|
size_t size() const { return m_entries.size(); }
|
|
Program const* find(std::string const& _abbreviatedOptimisationSteps) const;
|
|
bool contains(std::string const& _abbreviatedOptimisationSteps) const { return find(_abbreviatedOptimisationSteps) != nullptr; }
|
|
|
|
CacheStats gatherStats() const;
|
|
|
|
std::map<std::string, CacheEntry> const& entries() const { return m_entries; }
|
|
Program const& program() const { return m_program; }
|
|
size_t currentRound() const { return m_currentRound; }
|
|
|
|
private:
|
|
size_t calculateTotalCachedCodeSize() const;
|
|
std::map<size_t, size_t> countRoundEntries() const;
|
|
|
|
// The best matching data structure here would be a trie of chromosome prefixes but since
|
|
// the programs are orders of magnitude larger than the prefixes, it does not really matter.
|
|
// A map should be good enough.
|
|
std::map<std::string, CacheEntry> m_entries;
|
|
|
|
Program m_program;
|
|
size_t m_currentRound = 0;
|
|
size_t m_hits = 0;
|
|
size_t m_misses = 0;
|
|
};
|
|
|
|
}
|