Change semantics tests to use the proper via IR pipeline and make optimizer requirements explicit.

This commit is contained in:
Daniel Kirchner 2021-09-13 20:37:16 +02:00
parent 8fafdeacac
commit 153b0bf4ba
3 changed files with 94 additions and 50 deletions

View File

@ -81,6 +81,16 @@ SemanticTest::SemanticTest(
m_enforceCompileToEwasm = false;
}
string requiresYulOptimizer = m_reader.stringSetting("requiresYulOptimizer", "false");
if (requiresYulOptimizer == "false")
m_requiresYulOptimizer = RequiresYulOptimizer::False;
else if (requiresYulOptimizer == "minimalStack")
m_requiresYulOptimizer = RequiresYulOptimizer::MinimalStack;
else if (requiresYulOptimizer == "full")
m_requiresYulOptimizer = RequiresYulOptimizer::Full;
else
BOOST_THROW_EXCEPTION(runtime_error("Invalid requiresYulOptimizer value: " + requiresYulOptimizer + "."));
string compileToEwasm = m_reader.stringSetting("compileToEwasm", "false");
if (compileToEwasm == "also")
m_testCaseWantsEwasmRun = true;
@ -307,7 +317,52 @@ TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePref
result = runTest(_stream, _linePrefix, _formatted, false, false);
if ((m_testCaseWantsYulRun || m_enforceViaYul) && result == TestResult::Success)
{
if (solidity::test::CommonOptions::get().optimize)
result = runTest(_stream, _linePrefix, _formatted, true, false);
else
for (auto requiresYulOptimizer: {RequiresYulOptimizer::False, RequiresYulOptimizer::MinimalStack, RequiresYulOptimizer::Full})
{
ScopedSaveAndRestore optimizerSettings(m_optimiserSettings, OptimiserSettings::minimal());
if (requiresYulOptimizer == RequiresYulOptimizer::MinimalStack)
{
m_optimiserSettings.runYulOptimiser = true;
m_optimiserSettings.yulOptimiserSteps = "uljmul jmul";
}
else if (requiresYulOptimizer == RequiresYulOptimizer::Full)
m_optimiserSettings = OptimiserSettings::full();
try
{
result = runTest(_stream, _linePrefix, _formatted, true, false);
if (m_requiresYulOptimizer != requiresYulOptimizer)
{
static constexpr auto settingToString = [](RequiresYulOptimizer _setting) {
switch (_setting)
{
case RequiresYulOptimizer::False:
return "false";
case RequiresYulOptimizer::MinimalStack:
return "minimalStack";
case RequiresYulOptimizer::Full:
return "full";
}
return "unknown";
};
m_canUpdateYulOptimizerRequirement = requiresYulOptimizer;
AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) << _linePrefix << endl
<< _linePrefix << "requiresYulOptimizer is set to " << settingToString(m_requiresYulOptimizer)
<< " but should be " << settingToString(requiresYulOptimizer) << endl;
return TestResult::Failure;
}
break;
}
catch (yul::StackTooDeepError const&)
{
if (requiresYulOptimizer == RequiresYulOptimizer::Full)
throw;
}
}
}
if ((m_testCaseWantsEwasmRun || m_enforceCompileToEwasm) && result == TestResult::Success)
{
@ -330,7 +385,8 @@ TestCase::TestResult SemanticTest::runTest(
string const& _linePrefix,
bool _formatted,
bool _isYulRun,
bool _isEwasmRun)
bool _isEwasmRun
)
{
bool success = true;
m_gasCostFailure = false;
@ -666,11 +722,26 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre
}
else if (m_canEnableYulRun)
_stream << _linePrefix << "// compileViaYul: also\n";
if (m_canUpdateYulOptimizerRequirement)
{
switch (*m_canUpdateYulOptimizerRequirement)
{
case RequiresYulOptimizer::False:
break;
case RequiresYulOptimizer::MinimalStack:
_stream << _linePrefix << "// requiresYulOptimizer: minimalStack\n";
break;
case RequiresYulOptimizer::Full:
_stream << _linePrefix << "// requiresYulOptimizer: full\n";
break;
}
}
for (auto const& [settingName, settingValue]: settings)
if (
!(settingName == "compileToEwasm" && m_canEnableEwasmRun) &&
!(settingName == "compileViaYul" && (m_canEnableYulRun || m_canEnableEwasmRun))
!(settingName == "compileViaYul" && (m_canEnableYulRun || m_canEnableEwasmRun)) &&
!(settingName == "requiresYulOptimizer" && m_canUpdateYulOptimizerRequirement)
)
_stream << _linePrefix << "// " << settingName << ": " << settingValue<< endl;
}

View File

@ -85,7 +85,17 @@ public:
bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {});
private:
TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _isYulRun, bool _isEwasmRun);
enum class RequiresYulOptimizer
{
False, MinimalStack, Full
};
TestResult runTest(
std::ostream& _stream,
std::string const& _linePrefix,
bool _formatted,
bool _isYulRun,
bool _isEwasmRun
);
bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const;
std::map<std::string, Builtin> makeBuiltins();
std::vector<SideEffectHook> makeSideEffectHooks() const;
@ -108,6 +118,8 @@ private:
bool m_canEnableEwasmRun = false;
bool m_gasCostFailure = false;
bool m_enforceGasCost = false;
RequiresYulOptimizer m_requiresYulOptimizer = RequiresYulOptimizer::False;
std::optional<RequiresYulOptimizer> m_canUpdateYulOptimizerRequirement;
u256 m_enforceGasCostMinValue;
};

View File

@ -25,6 +25,7 @@
#include <iostream>
#include <boost/test/framework.hpp>
#include <test/libsolidity/SolidityExecutionFramework.h>
#include <libyul/Exceptions.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/SourceReferenceFormatter.h>
@ -49,14 +50,15 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
m_compiler.reset();
m_compiler.enableEwasmGeneration(m_compileToEwasm);
m_compiler.setSources(sourcesWithPreamble);
m_compiler.enableEvmBytecodeGeneration(!m_compileToEwasm);
m_compiler.setViaIR(m_compileViaYul || m_compileToEwasm);
m_compiler.setSources(move(sourcesWithPreamble));
m_compiler.setLibraries(_libraryAddresses);
m_compiler.setRevertStringBehaviour(m_revertStrings);
m_compiler.setEVMVersion(m_evmVersion);
m_compiler.setOptimiserSettings(m_optimiserSettings);
m_compiler.enableEvmBytecodeGeneration(!m_compileViaYul);
m_compiler.enableIRGeneration(m_compileViaYul);
m_compiler.setRevertStringBehaviour(m_revertStrings);
if (!m_compiler.compile())
{
// The testing framework expects an exception for
@ -69,50 +71,9 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
.printErrorInformation(m_compiler.errors());
BOOST_ERROR("Compiling contract failed");
}
string contractName(_contractName.empty() ? m_compiler.lastContractName(_mainSourceName) : _contractName);
evmasm::LinkerObject obj;
if (m_compileViaYul)
{
if (m_compileToEwasm)
obj = m_compiler.ewasmObject(contractName);
else
{
// Try compiling twice: If the first run fails due to stack errors, forcefully enable
// the optimizer.
for (bool forceEnableOptimizer: {false, true})
{
OptimiserSettings optimiserSettings = m_optimiserSettings;
if (!forceEnableOptimizer && !optimiserSettings.runYulOptimiser)
{
// Enable some optimizations on the first run
optimiserSettings.runYulOptimiser = true;
optimiserSettings.yulOptimiserSteps = "uljmul jmul";
}
else if (forceEnableOptimizer)
optimiserSettings = OptimiserSettings::full();
yul::AssemblyStack
asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, optimiserSettings);
bool analysisSuccessful = asmStack.parseAndAnalyze("", m_compiler.yulIROptimized(contractName));
solAssert(analysisSuccessful, "Code that passed analysis in CompilerStack can't have errors");
try
{
asmStack.optimize();
obj = move(*asmStack.assemble(yul::AssemblyStack::Machine::EVM).bytecode);
obj.link(_libraryAddresses);
break;
}
catch (...)
{
if (forceEnableOptimizer || optimiserSettings == OptimiserSettings::full())
throw;
}
}
}
}
else
obj = m_compiler.object(contractName);
evmasm::LinkerObject obj = m_compileToEwasm ? m_compiler.ewasmObject(contractName) : m_compiler.object(contractName);
BOOST_REQUIRE(obj.linkReferences.empty());
if (m_showMetadata)
cout << "metadata: " << m_compiler.metadata(contractName) << endl;