Skip to content
Merged
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
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