Skip to content
Closed
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
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Language Features:


Compiler Features:
* Commandline Interface: Accept nested brackets in step sequences passed to ``--yul-optimizations``.
* SMTChecker: Output values for ``block.*``, ``msg.*`` and ``tx.*`` variables that are present in the called functions.
* Standard JSON: Accept nested brackets in step sequences passed to ``settings.optimizer.details.yulDetails.optimizerSteps``.


Bugfixes:
Expand Down
134 changes: 86 additions & 48 deletions libyul/optimiser/Suite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
#include <range/v3/view/map.hpp>
#include <range/v3/action/remove.hpp>

#include <limits>
#include <tuple>

using namespace std;
using namespace solidity;
using namespace solidity::yul;
Expand All @@ -87,7 +90,7 @@ void OptimiserSuite::run(
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
string const& _optimisationSequence,
string_view _optimisationSequence,
optional<size_t> _expectedExecutionsPerDeployment,
set<YulString> const& _externallyUsedIdentifiers
)
Expand Down Expand Up @@ -267,22 +270,22 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
return lookupTable;
}

void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
{
bool insideLoop = false;
int8_t nestingLevel = 0;
for (char abbreviation: _stepAbbreviations)
switch (abbreviation)
{
case ' ':
case '\n':
break;
case '[':
assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported");
insideLoop = true;
assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep");
nestingLevel++;
break;
case ']':
assertThrow(insideLoop, OptimizerException, "Unbalanced brackets");
insideLoop = false;
nestingLevel--;
assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
break;
default:
{
Expand All @@ -301,43 +304,99 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
OptimizerException,
"'"s + abbreviation + "' is invalid in the current environment: " + *invalid
);

}
}
assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets");
assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets");
}

void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable)
{
validateSequence(_stepAbbreviations);

string input = _stepAbbreviations;
ranges::actions::remove(input, ' ');
ranges::actions::remove(input, '\n');
// This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'.
auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view>
{
for (size_t i = 0; i < _tail.size(); ++i)
{
yulAssert(_tail[i] != ']');
if (_tail[i] == '[')
return {_tail.substr(0, i), _tail.substr(i)};
}
return {_tail, {}};
};

auto abbreviationsToSteps = [](string const& _sequence) -> vector<string>
// This splits '[bbb]ccc...' into 'bbb' and 'ccc...'.
auto extractBracketContent = [](string_view _tail) -> tuple<string_view, string_view>
{
yulAssert(!_tail.empty() && _tail[0] == '[');

size_t contentLength = 0;
int8_t nestingLevel = 1;
for (char abbreviation: _tail.substr(1))
{
if (abbreviation == '[')
{
yulAssert(nestingLevel < numeric_limits<int8_t>::max());
++nestingLevel;
}
else if (abbreviation == ']')
{
--nestingLevel;
if (nestingLevel == 0)
break;
}
++contentLength;
}
yulAssert(nestingLevel == 0);
yulAssert(_tail[contentLength + 1] == ']');

return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)};
};

auto abbreviationsToSteps = [](string_view _sequence) -> vector<string>
{
vector<string> steps;
for (char abbreviation: _sequence)
steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
if (abbreviation != ' ' && abbreviation != '\n')
steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
return steps;
};

// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]`
// `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have
// four segments, the last of which is an empty bracket.
size_t currentPairStart = 0;
while (currentPairStart < input.size())
vector<tuple<string_view, bool>> subsequences;
string_view tail = _stepAbbreviations;
while (!tail.empty())
{
size_t openingBracket = input.find('[', currentPairStart);
size_t closingBracket = input.find(']', openingBracket);
size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1);
yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), "");
string_view subsequence;
tie(subsequence, tail) = extractNonNestedPrefix(tail);
if (subsequence.size() > 0)
subsequences.push_back({subsequence, false});

runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast);
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast);
if (tail.empty())
break;

currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1);
tie(subsequence, tail) = extractBracketContent(tail);
if (subsequence.size() > 0)
subsequences.push_back({subsequence, true});
}

size_t codeSize = 0;
for (size_t round = 0; round < MaxRounds; ++round)
{
for (auto const& [subsequence, repeat]: subsequences)
{
if (repeat)
runSequence(subsequence, _ast, true);
else
runSequence(abbreviationsToSteps(subsequence), _ast);
}

if (!_repeatUntilStable)
break;

size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast);
if (newSize == codeSize)
break;
codeSize = newSize;
}
}

Expand Down Expand Up @@ -365,24 +424,3 @@ void OptimiserSuite::runSequence(std::vector<string> const& _steps, Block& _ast)
}
}
}

void OptimiserSuite::runSequenceUntilStable(
std::vector<string> const& _steps,
Block& _ast,
size_t maxRounds
)
{
if (_steps.empty())
return;

size_t codeSize = 0;
for (size_t rounds = 0; rounds < maxRounds; ++rounds)
{
size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast);
if (newSize == codeSize)
break;
codeSize = newSize;

runSequence(_steps, _ast);
}
}
12 changes: 4 additions & 8 deletions libyul/optimiser/Suite.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include <set>
#include <string>
#include <string_view>
#include <memory>

namespace solidity::yul
Expand Down Expand Up @@ -64,22 +65,17 @@ class OptimiserSuite
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
std::string const& _optimisationSequence,
std::string_view _optimisationSequence,
std::optional<size_t> _expectedExecutionsPerDeployment,
std::set<YulString> const& _externallyUsedIdentifiers = {}
);

/// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
/// @throw OptimizerException if the sequence is invalid
static void validateSequence(std::string const& _stepAbbreviations);
static void validateSequence(std::string_view _stepAbbreviations);

void runSequence(std::vector<std::string> const& _steps, Block& _ast);
void runSequence(std::string const& _stepAbbreviations, Block& _ast);
void runSequenceUntilStable(
std::vector<std::string> const& _steps,
Block& _ast,
size_t maxRounds = MaxRounds
);
void runSequence(std::string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable = false);

static std::map<std::string, std::unique_ptr<OptimiserStep>> const& allSteps();
static std::map<std::string, char> const& stepNameToAbbreviationMap();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"language": "Solidity",
"sources": {
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_nested_brackets/in.sol"]}
},
"settings": {
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[[a][[aa]aa[aa]][]]aaa[aa[aa[aa]]]a[a][a][a]a[a]"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"sources":
{
"A":
{
"id": 0
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"language": "Solidity",
"sources": {
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_nesting_too_deep/in.sol"]}
},
"settings": {
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[a]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[
{
"component": "general",
"formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported",
"message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported",
"formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Brackets nested too deep",
"message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Brackets nested too deep",
"severity": "error",
"type": "JSONError"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--pretty-json --json-indent 4
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;

contract C {
function f() public pure {}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"language": "Solidity",
"sources": {
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/in.sol"]}
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_unbalanced_closing_bracket/in.sol"]}
},
"settings": {
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[a][aa[aa]]a"
"optimizerSteps": "a]a]["
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--pretty-json --json-indent 4
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;

contract C {
function f() public pure {}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"language": "Solidity",
"sources": {
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/in.sol"]}
"A": {"urls": ["standard_optimizer_yulDetails_optimiserSteps_unbalanced_opening_bracket/in.sol"]}
},
"settings": {
"optimizer": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"errors":
[
{
"component": "general",
"formattedMessage": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets",
"message": "Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets",
"severity": "error",
"type": "JSONError"
}
]
}
1 change: 0 additions & 1 deletion test/cmdlineTests/yul_optimizer_steps_invalid_nesting/args

This file was deleted.

1 change: 0 additions & 1 deletion test/cmdlineTests/yul_optimizer_steps_invalid_nesting/err

This file was deleted.

1 change: 1 addition & 0 deletions test/cmdlineTests/yul_optimizer_steps_nested_brackets/args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--ir-optimized --optimize --yul-optimizations a[[a][[aa]aa[aa]][]]aaa[aa[aa[aa]]]a[a][a][a]a[a]
Loading