SemanticTest: Add a setting that explicitly states the minimal Yul optimization level that avoids "Stack too deep"

This commit is contained in:
Kamil Śliwak 2023-08-22 20:00:00 +02:00
parent 8e9496eb8b
commit bc6d10f11d
2 changed files with 123 additions and 5 deletions

View File

@ -45,6 +45,17 @@ using namespace boost::algorithm;
using namespace boost::unit_test; using namespace boost::unit_test;
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
ostream& solidity::frontend::test::operator<<(ostream& _output, RequiresYulOptimizer _requiresYulOptimizer)
{
switch (_requiresYulOptimizer)
{
case RequiresYulOptimizer::False: _output << "false"; break;
case RequiresYulOptimizer::MinimalStack: _output << "minimalStack"; break;
case RequiresYulOptimizer::Full: _output << "full"; break;
}
return _output;
}
SemanticTest::SemanticTest( SemanticTest::SemanticTest(
string const& _filename, string const& _filename,
langutil::EVMVersion _evmVersion, langutil::EVMVersion _evmVersion,
@ -66,6 +77,16 @@ SemanticTest::SemanticTest(
static set<string> const yulRunTriggers{"also", "true"}; static set<string> const yulRunTriggers{"also", "true"};
static set<string> const legacyRunTriggers{"also", "false", "default"}; static set<string> const legacyRunTriggers{"also", "false", "default"};
m_requiresYulOptimizer = m_reader.enumSetting<RequiresYulOptimizer>(
"requiresYulOptimizer",
{
{toString(RequiresYulOptimizer::False), RequiresYulOptimizer::False},
{toString(RequiresYulOptimizer::MinimalStack), RequiresYulOptimizer::MinimalStack},
{toString(RequiresYulOptimizer::Full), RequiresYulOptimizer::Full},
},
toString(RequiresYulOptimizer::False)
);
m_runWithABIEncoderV1Only = m_reader.boolSetting("ABIEncoderV1Only", false); m_runWithABIEncoderV1Only = m_reader.boolSetting("ABIEncoderV1Only", false);
if (m_runWithABIEncoderV1Only && !solidity::test::CommonOptions::get().useABIEncoderV1) if (m_runWithABIEncoderV1Only && !solidity::test::CommonOptions::get().useABIEncoderV1)
m_shouldRun = false; m_shouldRun = false;
@ -272,15 +293,38 @@ optional<AnnotatedEventSignature> SemanticTest::matchEvent(util::h256 const& has
return result; return result;
} }
frontend::OptimiserSettings SemanticTest::optimizerSettingsFor(RequiresYulOptimizer _requiresYulOptimizer)
{
switch (_requiresYulOptimizer) {
case RequiresYulOptimizer::False:
return OptimiserSettings::minimal();
case RequiresYulOptimizer::MinimalStack:
{
OptimiserSettings settings = OptimiserSettings::minimal();
settings.runYulOptimiser = true;
settings.yulOptimiserSteps = "uljmul jmul";
return settings;
}
case RequiresYulOptimizer::Full:
return OptimiserSettings::full();
}
unreachable();
}
TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
{ {
TestResult result = TestResult::Success; TestResult result = TestResult::Success;
if (m_testCaseWantsLegacyRun && !m_eofVersion.has_value()) if (m_testCaseWantsLegacyRun && !m_eofVersion.has_value())
result = runTest(_stream, _linePrefix, _formatted, false); result = runTest(_stream, _linePrefix, _formatted, false /* _isYulRun */);
if (m_testCaseWantsYulRun && result == TestResult::Success) if (m_testCaseWantsYulRun && result == TestResult::Success)
result = runTest(_stream, _linePrefix, _formatted, true); {
if (solidity::test::CommonOptions::get().optimize)
result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */);
else
result = tryRunTestWithYulOptimizer(_stream, _linePrefix, _formatted);
}
if (result != TestResult::Success) if (result != TestResult::Success)
solidity::test::CommonOptions::get().printSelectedOptions( solidity::test::CommonOptions::get().printSelectedOptions(
@ -296,7 +340,8 @@ TestCase::TestResult SemanticTest::runTest(
ostream& _stream, ostream& _stream,
string const& _linePrefix, string const& _linePrefix,
bool _formatted, bool _formatted,
bool _isYulRun) bool _isYulRun
)
{ {
bool success = true; bool success = true;
m_gasCostFailure = false; m_gasCostFailure = false;
@ -470,6 +515,53 @@ TestCase::TestResult SemanticTest::runTest(
return TestResult::Success; return TestResult::Success;
} }
TestCase::TestResult SemanticTest::tryRunTestWithYulOptimizer(
std::ostream& _stream,
std::string const& _linePrefix,
bool _formatted
)
{
TestResult result{};
for (auto requiresYulOptimizer: {
RequiresYulOptimizer::False,
RequiresYulOptimizer::MinimalStack,
RequiresYulOptimizer::Full,
})
{
ScopedSaveAndRestore optimizerSettings(
m_optimiserSettings,
optimizerSettingsFor(requiresYulOptimizer)
);
try
{
result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */);
}
catch (yul::StackTooDeepError const&)
{
if (requiresYulOptimizer == RequiresYulOptimizer::Full)
throw;
else
continue;
}
if (m_requiresYulOptimizer != requiresYulOptimizer && result != TestResult::FatalError)
{
soltestAssert(result == TestResult::Success || result == TestResult::Failure);
AnsiColorized(_stream, _formatted, {BOLD, YELLOW})
<< _linePrefix << endl
<< _linePrefix << "requiresYulOptimizer is set to " << m_requiresYulOptimizer
<< " but should be " << requiresYulOptimizer << endl;
m_requiresYulOptimizer = requiresYulOptimizer;
return TestResult::Failure;
}
return result;
}
unreachable();
}
bool SemanticTest::checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const bool SemanticTest::checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const
{ {
string setting = string setting =
@ -581,7 +673,10 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre
_stream << _linePrefix << "// ====" << endl; _stream << _linePrefix << "// ====" << endl;
for (auto const& [settingName, settingValue]: settings) for (auto const& [settingName, settingValue]: settings)
_stream << _linePrefix << "// " << settingName << ": " << settingValue<< endl; if (settingName == "requiresYulOptimizer")
_stream << _linePrefix << "// " << settingName << ": " << m_requiresYulOptimizer << endl;
else
_stream << _linePrefix << "// " << settingName << ": " << settingValue<< endl;
} }
void SemanticTest::parseExpectations(istream& _stream) void SemanticTest::parseExpectations(istream& _stream)

View File

@ -37,6 +37,15 @@ struct AnnotatedEventSignature
std::vector<std::string> nonIndexedTypes; std::vector<std::string> nonIndexedTypes;
}; };
enum class RequiresYulOptimizer
{
False,
MinimalStack,
Full,
};
std::ostream& operator<<(std::ostream& _output, RequiresYulOptimizer _requiresYulOptimizer);
/** /**
* Class that represents a semantic test (or end-to-end test) and allows running it as part of the * Class that represents a semantic test (or end-to-end test) and allows running it as part of the
* boost unit test environment or isoltest. It reads the Solidity source and an additional comment * boost unit test environment or isoltest. It reads the Solidity source and an additional comment
@ -83,13 +92,26 @@ public:
bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {}); bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {});
private: private:
TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _isYulRun); TestResult runTest(
std::ostream& _stream,
std::string const& _linePrefix,
bool _formatted,
bool _isYulRun
);
TestResult tryRunTestWithYulOptimizer(
std::ostream& _stream,
std::string const& _linePrefix,
bool _formatted
);
bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const; bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const;
std::map<std::string, Builtin> makeBuiltins(); std::map<std::string, Builtin> makeBuiltins();
std::vector<SideEffectHook> makeSideEffectHooks() const; std::vector<SideEffectHook> makeSideEffectHooks() const;
std::vector<std::string> eventSideEffectHook(FunctionCall const&) const; std::vector<std::string> eventSideEffectHook(FunctionCall const&) const;
std::optional<AnnotatedEventSignature> matchEvent(util::h256 const& hash) const; std::optional<AnnotatedEventSignature> matchEvent(util::h256 const& hash) const;
static std::string formatEventParameter(std::optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data); static std::string formatEventParameter(std::optional<AnnotatedEventSignature> _signature, bool _indexed, size_t _index, bytes const& _data);
OptimiserSettings optimizerSettingsFor(RequiresYulOptimizer _requiresYulOptimizer);
SourceMap m_sources; SourceMap m_sources;
std::size_t m_lineOffset; std::size_t m_lineOffset;
std::vector<TestFunctionCall> m_tests; std::vector<TestFunctionCall> m_tests;
@ -101,6 +123,7 @@ private:
bool m_allowNonExistingFunctions = false; bool m_allowNonExistingFunctions = false;
bool m_gasCostFailure = false; bool m_gasCostFailure = false;
bool m_enforceGasCost = false; bool m_enforceGasCost = false;
RequiresYulOptimizer m_requiresYulOptimizer{};
u256 m_enforceGasCostMinValue; u256 m_enforceGasCostMinValue;
}; };