Skip to content

Commit

Permalink
Merge pull request #14511 from ethereum/semantic-tests-via-ir
Browse files Browse the repository at this point in the history
Semantic tests via IR
  • Loading branch information
ekpyron authored Aug 23, 2023
2 parents 589adee + 1bb5960 commit 78b1f5a
Show file tree
Hide file tree
Showing 47 changed files with 222 additions and 124 deletions.
107 changes: 102 additions & 5 deletions test/libsolidity/SemanticTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ using namespace boost::algorithm;
using namespace boost::unit_test;
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(
string const& _filename,
langutil::EVMVersion _evmVersion,
Expand All @@ -66,6 +77,16 @@ SemanticTest::SemanticTest(
static set<string> const yulRunTriggers{"also", "true"};
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);
if (m_runWithABIEncoderV1Only && !solidity::test::CommonOptions::get().useABIEncoderV1)
m_shouldRun = false;
Expand Down Expand Up @@ -272,15 +293,39 @@ optional<AnnotatedEventSignature> SemanticTest::matchEvent(util::h256 const& has
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)
{
TestResult result = TestResult::Success;

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)
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)
solidity::test::CommonOptions::get().printSelectedOptions(
Expand All @@ -296,7 +341,8 @@ TestCase::TestResult SemanticTest::runTest(
ostream& _stream,
string const& _linePrefix,
bool _formatted,
bool _isYulRun)
bool _isYulRun
)
{
bool success = true;
m_gasCostFailure = false;
Expand Down Expand Up @@ -470,6 +516,53 @@ TestCase::TestResult SemanticTest::runTest(
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
{
string setting =
Expand Down Expand Up @@ -576,12 +669,16 @@ void SemanticTest::printUpdatedExpectations(ostream& _stream, string const&) con
void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePrefix)
{
auto& settings = m_reader.settings();
if (settings.empty())
if (settings.empty() && m_requiresYulOptimizer == RequiresYulOptimizer::False)
return;

_stream << _linePrefix << "// ====" << endl;
if (m_requiresYulOptimizer != RequiresYulOptimizer::False)
_stream << _linePrefix << "// requiresYulOptimizer: " << m_requiresYulOptimizer << endl;

for (auto const& [settingName, settingValue]: settings)
_stream << _linePrefix << "// " << settingName << ": " << settingValue<< endl;
if (settingName != "requiresYulOptimizer")
_stream << _linePrefix << "// " << settingName << ": " << settingValue<< endl;
}

void SemanticTest::parseExpectations(istream& _stream)
Expand Down
25 changes: 24 additions & 1 deletion test/libsolidity/SemanticTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ struct AnnotatedEventSignature
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
* boost unit test environment or isoltest. It reads the Solidity source and an additional comment
Expand Down Expand Up @@ -83,13 +92,26 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
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);
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;
std::map<std::string, Builtin> makeBuiltins();
std::vector<SideEffectHook> makeSideEffectHooks() const;
std::vector<std::string> eventSideEffectHook(FunctionCall const&) 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);

OptimiserSettings optimizerSettingsFor(RequiresYulOptimizer _requiresYulOptimizer);

SourceMap m_sources;
std::size_t m_lineOffset;
std::vector<TestFunctionCall> m_tests;
Expand All @@ -101,6 +123,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
bool m_allowNonExistingFunctions = false;
bool m_gasCostFailure = false;
bool m_enforceGasCost = false;
RequiresYulOptimizer m_requiresYulOptimizer{};
u256 m_enforceGasCostMinValue;
};

Expand Down
34 changes: 26 additions & 8 deletions test/libsolidity/SolidityEndToEndTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include <libsolutil/Keccak256.h>
#include <libsolutil/ErrorCodes.h>

#include <libyul/Exceptions.h>

#include <boost/test/unit_test.hpp>

#include <range/v3/view/transform.hpp>
Expand All @@ -51,16 +53,32 @@ using namespace solidity::util;
using namespace solidity::test;
using namespace solidity::langutil;

#define ALSO_VIA_YUL(CODE) \
{ \
m_compileViaYul = false; \
{ CODE } \
\
m_compileViaYul = true; \
reset(); \
{ CODE } \
#define ALSO_VIA_YUL(CODE) \
{ \
m_compileViaYul = false; \
RUN_AND_RERUN_WITH_OPTIMIZER_ON_STACK_ERROR(CODE) \
\
m_compileViaYul = true; \
reset(); \
RUN_AND_RERUN_WITH_OPTIMIZER_ON_STACK_ERROR(CODE) \
}

#define RUN_AND_RERUN_WITH_OPTIMIZER_ON_STACK_ERROR(CODE) \
{ \
try \
{ CODE } \
catch (yul::StackTooDeepError const&) \
{ \
if (m_optimiserSettings == OptimiserSettings::full()) \
throw; \
\
reset(); \
m_optimiserSettings = OptimiserSettings::full(); \
{ CODE } \
} \
}


namespace solidity::frontend::test
{

Expand Down
50 changes: 5 additions & 45 deletions test/libsolidity/SolidityExecutionFramework.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <test/libsolidity/util/Common.h>

#include <liblangutil/DebugInfoSelection.h>
#include <libyul/Exceptions.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/SourceReferenceFormatter.h>

Expand Down Expand Up @@ -60,13 +61,14 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
m_compiler.setEVMVersion(m_evmVersion);
m_compiler.setEOFVersion(m_eofVersion);
m_compiler.setOptimiserSettings(m_optimiserSettings);
m_compiler.enableEvmBytecodeGeneration(!m_compileViaYul);
m_compiler.enableIRGeneration(m_compileViaYul);
m_compiler.enableEvmBytecodeGeneration(true);
m_compiler.setViaIR(m_compileViaYul);
m_compiler.setRevertStringBehaviour(m_revertStrings);
if (!m_appendCBORMetadata) {
m_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata);
}
m_compiler.setMetadataHash(m_metadataHash);

if (!m_compiler.compile())
{
// The testing framework expects an exception for
Expand All @@ -80,49 +82,7 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
BOOST_ERROR("Compiling contract failed");
}
string contractName(_contractName.empty() ? m_compiler.lastContractName(_mainSourceName) : _contractName);
evmasm::LinkerObject obj;
if (m_compileViaYul)
{
// 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::YulStack asmStack(
m_evmVersion,
m_eofVersion,
yul::YulStack::Language::StrictAssembly,
optimiserSettings,
DebugInfoSelection::All()
);
bool analysisSuccessful = asmStack.parseAndAnalyze("", m_compiler.yulIROptimized(contractName));
solAssert(analysisSuccessful, "Code that passed analysis in CompilerStack can't have errors");

try
{
asmStack.optimize();
obj = std::move(*asmStack.assemble(yul::YulStack::Machine::EVM).bytecode);
obj.link(_libraryAddresses);
break;
}
catch (...)
{
if (forceEnableOptimizer || optimiserSettings == OptimiserSettings::full())
throw;
}
}
}
else
obj = m_compiler.object(contractName);
evmasm::LinkerObject obj = m_compiler.object(contractName);
BOOST_REQUIRE(obj.linkReferences.empty());
if (m_showMetadata)
cout << "metadata: " << m_compiler.metadata(contractName) << endl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ contract C is B {
}
// ----
// test() -> 77
// gas irOptimized: 110731
// gas irOptimized: 110325
// gas legacy: 151866
// gas legacyOptimized: 110359
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract C {
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1, 5, 6 -> 0x20, 0x40, 5, 2
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1 -> FAILURE
// f_storage(uint256[],uint256[2]): 0x20, 1, 2 -> 0x20, 0x60, 0x20, 1, 2
// gas irOptimized: 111639
// gas irOptimized: 111642
// gas legacy: 112944
// gas legacyOptimized: 112092
// f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5, 6 -> 0x20, 0x80, 0x20, 2, 5, 6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ contract C {
// test_boundary_check(uint256,uint256): 1, 1 -> FAILURE, hex"4e487b71", 0x32
// test_boundary_check(uint256,uint256): 10, 10 -> FAILURE, hex"4e487b71", 0x32
// test_boundary_check(uint256,uint256): 256, 256 -> FAILURE, hex"4e487b71", 0x32
// gas irOptimized: 137904
// gas irOptimized: 137913
// gas legacy: 133633
// gas legacyOptimized: 114354
// test_boundary_check(uint256,uint256): 256, 255 -> 0
// gas irOptimized: 140039
// gas irOptimized: 140048
// gas legacy: 135949
// gas legacyOptimized: 116533
// test_boundary_check(uint256,uint256): 256, 0xFFFF -> FAILURE, hex"4e487b71", 0x32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ contract c {
}
// ----
// test() -> 0
// gas irOptimized: 125212
// gas irOptimized: 125058
// gas legacy: 150372
// gas legacyOptimized: 146391
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ contract c {
}
// ----
// test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x05000000000000000000000000000000000000000000000000
// gas irOptimized: 208044
// gas irOptimized: 208053
// gas legacy: 221769
// gas legacyOptimized: 220611
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ contract c {
}
// ----
// test(uint256[2][]): 32, 3, 7, 8, 9, 10, 11, 12 -> 10
// gas irOptimized: 689666
// gas irOptimized: 689654
// gas legacy: 686178
// gas legacyOptimized: 685628
Loading

0 comments on commit 78b1f5a

Please sign in to comment.