diff options
24 files changed, 755 insertions, 101 deletions
diff --git a/libsolidity/codegen/AsmCodeGen.cpp b/libsolidity/codegen/AsmCodeGen.cpp index 83dd08fb..dfcc900b 100644 --- a/libsolidity/codegen/AsmCodeGen.cpp +++ b/libsolidity/codegen/AsmCodeGen.cpp @@ -27,10 +27,13 @@ #include <libyul/backends/evm/EVMCodeTransform.h> #include <libevmasm/Assembly.h> +#include <libevmasm/AssemblyItem.h> #include <libevmasm/Instruction.h> #include <liblangutil/SourceLocation.h> +#include <libdevcore/FixedHash.h> + #include <memory> #include <functional> @@ -40,97 +43,136 @@ using namespace langutil; using namespace yul; using namespace dev::solidity; -class EthAssemblyAdapter: public AbstractAssembly -{ -public: - explicit EthAssemblyAdapter(eth::Assembly& _assembly): - m_assembly(_assembly) - { - } - virtual void setSourceLocation(SourceLocation const& _location) override - { - m_assembly.setSourceLocation(_location); - } - virtual int stackHeight() const override { return m_assembly.deposit(); } - virtual void appendInstruction(solidity::Instruction _instruction) override - { - m_assembly.append(_instruction); - } - virtual void appendConstant(u256 const& _constant) override - { - m_assembly.append(_constant); - } - /// Append a label. - virtual void appendLabel(LabelID _labelId) override - { - m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId)); - } - /// Append a label reference. - virtual void appendLabelReference(LabelID _labelId) override - { - m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId)); - } - virtual size_t newLabelId() override - { - return assemblyTagToIdentifier(m_assembly.newTag()); - } - virtual size_t namedLabel(std::string const& _name) override - { - return assemblyTagToIdentifier(m_assembly.namedTag(_name)); - } - virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override - { - m_assembly.appendLibraryAddress(_linkerSymbol); - } - virtual void appendJump(int _stackDiffAfter) override - { - appendInstruction(solidity::Instruction::JUMP); - m_assembly.adjustDeposit(_stackDiffAfter); - } - virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override - { - appendLabelReference(_labelId); - appendJump(_stackDiffAfter); - } - virtual void appendJumpToIf(LabelID _labelId) override - { - appendLabelReference(_labelId); - appendInstruction(solidity::Instruction::JUMPI); - } - virtual void appendBeginsub(LabelID, int) override - { - // TODO we could emulate that, though - solAssert(false, "BEGINSUB not implemented for EVM 1.0"); - } - /// Call a subroutine. - virtual void appendJumpsub(LabelID, int, int) override - { - // TODO we could emulate that, though - solAssert(false, "JUMPSUB not implemented for EVM 1.0"); - } - - /// Return from a subroutine. - virtual void appendReturnsub(int, int) override - { - // TODO we could emulate that, though - solAssert(false, "RETURNSUB not implemented for EVM 1.0"); - } - - virtual void appendAssemblySize() override - { - m_assembly.appendProgramSize(); - } - -private: - static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) - { - u256 id = _tag.data(); - solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large."); - return LabelID(id); - } - - eth::Assembly& m_assembly; -}; +EthAssemblyAdapter::EthAssemblyAdapter(eth::Assembly& _assembly): + m_assembly(_assembly) +{ +} + +void EthAssemblyAdapter::setSourceLocation(SourceLocation const& _location) +{ + m_assembly.setSourceLocation(_location); +} + +int EthAssemblyAdapter::stackHeight() const +{ + return m_assembly.deposit(); +} + +void EthAssemblyAdapter::appendInstruction(solidity::Instruction _instruction) +{ + m_assembly.append(_instruction); +} + +void EthAssemblyAdapter::appendConstant(u256 const& _constant) +{ + m_assembly.append(_constant); +} + +void EthAssemblyAdapter::appendLabel(LabelID _labelId) +{ + m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId)); +} + +void EthAssemblyAdapter::appendLabelReference(LabelID _labelId) +{ + m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId)); +} + +size_t EthAssemblyAdapter::newLabelId() +{ + return assemblyTagToIdentifier(m_assembly.newTag()); +} + +size_t EthAssemblyAdapter::namedLabel(std::string const& _name) +{ + return assemblyTagToIdentifier(m_assembly.namedTag(_name)); +} + +void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol) +{ + m_assembly.appendLibraryAddress(_linkerSymbol); +} + +void EthAssemblyAdapter::appendJump(int _stackDiffAfter) +{ + appendInstruction(solidity::Instruction::JUMP); + m_assembly.adjustDeposit(_stackDiffAfter); +} + +void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter) +{ + appendLabelReference(_labelId); + appendJump(_stackDiffAfter); +} + +void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId) +{ + appendLabelReference(_labelId); + appendInstruction(solidity::Instruction::JUMPI); +} + +void EthAssemblyAdapter::appendBeginsub(LabelID, int) +{ + // TODO we could emulate that, though + solAssert(false, "BEGINSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendJumpsub(LabelID, int, int) +{ + // TODO we could emulate that, though + solAssert(false, "JUMPSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendReturnsub(int, int) +{ + // TODO we could emulate that, though + solAssert(false, "RETURNSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendAssemblySize() +{ + m_assembly.appendProgramSize(); +} + +pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly() +{ + shared_ptr<eth::Assembly> assembly{make_shared<eth::Assembly>()}; + auto sub = m_assembly.newSub(assembly); + return {make_shared<EthAssemblyAdapter>(*assembly), size_t(sub.data())}; +} + +void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub) +{ + auto it = m_dataHashBySubId.find(_sub); + if (it == m_dataHashBySubId.end()) + m_assembly.pushSubroutineOffset(size_t(_sub)); + else + m_assembly << eth::AssemblyItem(eth::PushData, it->second); +} + +void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub) +{ + auto it = m_dataHashBySubId.find(_sub); + if (it == m_dataHashBySubId.end()) + m_assembly.pushSubroutineSize(size_t(_sub)); + else + m_assembly << u256(m_assembly.data(h256(it->second)).size()); +} + +AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) +{ + eth::AssemblyItem pushData = m_assembly.newData(_data); + SubID subID = m_nextDataCounter++; + m_dataHashBySubId[subID] = pushData.data(); + return subID; +} + +EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(eth::AssemblyItem const& _tag) +{ + u256 id = _tag.data(); + solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large."); + return LabelID(id); +} void CodeGenerator::assemble( Block const& _parsedData, diff --git a/libsolidity/codegen/AsmCodeGen.h b/libsolidity/codegen/AsmCodeGen.h index dc529e10..4c6d97f4 100644 --- a/libsolidity/codegen/AsmCodeGen.h +++ b/libsolidity/codegen/AsmCodeGen.h @@ -21,6 +21,9 @@ #pragma once #include <libyul/AsmAnalysis.h> +#include <libyul/backends/evm/AbstractAssembly.h> + +#include <liblangutil/SourceLocation.h> #include <functional> @@ -34,11 +37,45 @@ namespace dev namespace eth { class Assembly; +class AssemblyItem; } namespace solidity { +class EthAssemblyAdapter: public yul::AbstractAssembly +{ +public: + explicit EthAssemblyAdapter(eth::Assembly& _assembly); + void setSourceLocation(langutil::SourceLocation const& _location) override; + int stackHeight() const override; + void appendInstruction(solidity::Instruction _instruction) override; + void appendConstant(u256 const& _constant) override; + void appendLabel(LabelID _labelId) override; + void appendLabelReference(LabelID _labelId) override; + size_t newLabelId() override; + size_t namedLabel(std::string const& _name) override; + void appendLinkerSymbol(std::string const& _linkerSymbol) override; + void appendJump(int _stackDiffAfter) override; + void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override; + void appendJumpToIf(LabelID _labelId) override; + void appendBeginsub(LabelID, int) override; + void appendJumpsub(LabelID, int, int) override; + void appendReturnsub(int, int) override; + void appendAssemblySize() override; + std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; + void appendDataOffset(SubID _sub) override; + void appendDataSize(SubID _sub) override; + SubID appendData(dev::bytes const& _data) override; + +private: + static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag); + + eth::Assembly& m_assembly; + std::map<SubID, dev::u256> m_dataHashBySubId; + size_t m_nextDataCounter = std::numeric_limits<size_t>::max() / 2; +}; + class CodeGenerator { public: diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index c15a192a..0fe57a45 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -29,6 +29,7 @@ #include <libyul/AsmParser.h> #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> +#include <libyul/backends/evm/EVMObjectCompiler.h> #include <libyul/backends/evm/EVMCodeTransform.h> #include <libyul/backends/evm/EVMAssembly.h> #include <libyul/ObjectParser.h> @@ -85,20 +86,42 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string void AssemblyStack::optimize() { solAssert(m_language != Language::Assembly, "Optimization requested for loose assembly."); - yul::OptimiserSuite::run(*m_parserResult->code, *m_parserResult->analysisInfo); + solAssert(m_analysisSuccessful, "Analysis was not successful."); + m_analysisSuccessful = false; + optimize(*m_parserResult); solAssert(analyzeParsed(), "Invalid source code after optimization."); } bool AssemblyStack::analyzeParsed() { solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); - m_parserResult->analysisInfo = make_shared<yul::AsmAnalysisInfo>(); - yul::AsmAnalyzer analyzer(*m_parserResult->analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToDialect(m_language)); - m_analysisSuccessful = analyzer.analyze(*m_parserResult->code); + m_analysisSuccessful = analyzeParsed(*m_parserResult); return m_analysisSuccessful; } +bool AssemblyStack::analyzeParsed(yul::Object& _object) +{ + solAssert(_object.code, ""); + _object.analysisInfo = make_shared<yul::AsmAnalysisInfo>(); + yul::AsmAnalyzer analyzer(*_object.analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToDialect(m_language)); + bool success = analyzer.analyze(*_object.code); + for (auto& subNode: _object.subObjects) + if (auto subObject = dynamic_cast<yul::Object*>(subNode.get())) + if (!analyzeParsed(*subObject)) + success = false; + return success; +} + +void AssemblyStack::optimize(yul::Object& _object) +{ + solAssert(_object.code, ""); + solAssert(_object.analysisInfo, ""); + for (auto& subNode: _object.subObjects) + if (auto subObject = dynamic_cast<yul::Object*>(subNode.get())) + optimize(*subObject); + yul::OptimiserSuite::run(*_object.code, *_object.analysisInfo); +} + MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { solAssert(m_analysisSuccessful, ""); @@ -112,7 +135,8 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { MachineAssemblyObject object; eth::Assembly assembly; - CodeGenerator::assemble(*m_parserResult->code, *m_parserResult->analysisInfo, assembly); + EthAssemblyAdapter adapter(assembly); + yul::EVMObjectCompiler::compile(*m_parserResult, adapter, m_language == Language::Yul, false); object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble()); object.assembly = assembly.assemblyString(); return object; @@ -121,7 +145,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { MachineAssemblyObject object; yul::EVMAssembly assembly(true); - yul::CodeTransform(assembly, *m_parserResult->analysisInfo, m_language == Language::Yul, true)(*m_parserResult->code); + yul::EVMObjectCompiler::compile(*m_parserResult, assembly, m_language == Language::Yul, true); object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); /// TODO: fill out text representation return object; diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 0d04ffec..485ec1e7 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -83,6 +83,9 @@ public: private: bool analyzeParsed(); + bool analyzeParsed(yul::Object& _object); + + void optimize(yul::Object& _object); Language m_language = Language::Assembly; EVMVersion m_evmVersion; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 2dec0a44..4be8155f 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(yul ObjectParser.cpp backends/evm/EVMAssembly.cpp backends/evm/EVMCodeTransform.cpp + backends/evm/EVMObjectCompiler.cpp optimiser/ASTCopier.cpp optimiser/ASTWalker.cpp optimiser/BlockFlattener.cpp diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 97b1d305..1f224ded 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -26,6 +26,7 @@ #include <libdevcore/CommonData.h> #include <functional> +#include <memory> namespace langutil { @@ -52,6 +53,7 @@ class AbstractAssembly { public: using LabelID = size_t; + using SubID = size_t; virtual ~AbstractAssembly() {} @@ -98,6 +100,14 @@ public: /// Append the assembled size as a constant. virtual void appendAssemblySize() = 0; + /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. + virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() = 0; + /// Appends the offset of the given sub-assembly or data. + virtual void appendDataOffset(SubID _sub) = 0; + /// Appends the size of the given sub-assembly or data. + virtual void appendDataSize(SubID _sub) = 0; + /// Appends the given data to the assembly and returns its ID. + virtual SubID appendData(dev::bytes const& _data) = 0; }; enum class IdentifierContext { LValue, RValue }; diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index 99506317..2cf9f001 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -194,6 +194,27 @@ void EVMAssembly::appendAssemblySize() m_bytecode += bytes(assemblySizeReferenceSize); } +pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EVMAssembly::createSubAssembly() +{ + solAssert(false, "Sub assemblies not implemented."); + return {}; +} + +void EVMAssembly::appendDataOffset(AbstractAssembly::SubID) +{ + solAssert(false, "Data not implemented."); +} + +void EVMAssembly::appendDataSize(AbstractAssembly::SubID) +{ + solAssert(false, "Data not implemented."); +} + +AbstractAssembly::SubID EVMAssembly::appendData(bytes const&) +{ + solAssert(false, "Data not implemented."); +} + void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) { solAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index d0a437cc..cef9c19a 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -77,6 +77,10 @@ public: /// Append the assembled size as a constant. void appendAssemblySize() override; + std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; + void appendDataOffset(SubID _sub) override; + void appendDataSize(SubID _sub) override; + SubID appendData(dev::bytes const& _data) override; /// Resolves references inside the bytecode and returns the linker object. dev::eth::LinkerObject finalize(); diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp new file mode 100644 index 00000000..e7e8ad99 --- /dev/null +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -0,0 +1,55 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Compiler that transforms Yul Objects to EVM bytecode objects. + */ + +#include <libyul/backends/evm/EVMObjectCompiler.h> + +#include <libyul/backends/evm/EVMCodeTransform.h> +#include <libyul/Object.h> +#include <libyul/Exceptions.h> + +using namespace yul; +using namespace std; + +void EVMObjectCompiler::compile(Object& _object, AbstractAssembly& _assembly, bool _yul, bool _evm15) +{ + EVMObjectCompiler compiler(_assembly, _yul, _evm15); + compiler.run(_object); +} + +void EVMObjectCompiler::run(Object& _object) +{ + map<YulString, AbstractAssembly::SubID> subIDs; + + for (auto& subNode: _object.subObjects) + if (Object* subObject = dynamic_cast<Object*>(subNode.get())) + { + auto subAssemblyAndID = m_assembly.createSubAssembly(); + subIDs[subObject->name] = subAssemblyAndID.second; + compile(*subObject, *subAssemblyAndID.first, m_yul, m_evm15); + } + else + { + Data const& data = dynamic_cast<Data const&>(*subNode); + subIDs[data.name] = m_assembly.appendData(data.data); + } + + yulAssert(_object.analysisInfo, "No analysis info."); + CodeTransform{m_assembly, *_object.analysisInfo, m_yul, m_evm15}(*_object.code); +} diff --git a/libyul/backends/evm/EVMObjectCompiler.h b/libyul/backends/evm/EVMObjectCompiler.h new file mode 100644 index 00000000..c7172e47 --- /dev/null +++ b/libyul/backends/evm/EVMObjectCompiler.h @@ -0,0 +1,43 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Compiler that transforms Yul Objects to EVM bytecode objects. + */ + + +namespace yul +{ +struct Object; +class AbstractAssembly; + +class EVMObjectCompiler +{ +public: + static void compile(Object& _object, AbstractAssembly& _assembly, bool _yul, bool _evm15); +private: + EVMObjectCompiler(AbstractAssembly& _assembly, bool _yul, bool _evm15): + m_assembly(_assembly), m_yul(_yul), m_evm15(_evm15) + {} + + void run(Object& _object); + + AbstractAssembly& m_assembly; + bool m_yul = false; + bool m_evm15 = false; +}; + +} diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 7cb0c143..ff443d11 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -40,6 +40,7 @@ #include <test/libsolidity/SyntaxTest.h> #include <test/libsolidity/SMTCheckerJSONTest.h> #include <test/libyul/YulOptimizerTest.h> +#include <test/libyul/ObjectCompilerTest.h> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> @@ -146,6 +147,12 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) "yulOptimizerTests", yul::test::YulOptimizerTest::create ) > 0, "no Yul Optimizer tests found"); + solAssert(registerTests( + master, + dev::test::Options::get().testPath / "libyul", + "objectCompiler", + yul::test::ObjectCompilerTest::create + ) > 0, "no Yul Object compiler tests found"); if (!dev::test::Options::get().disableSMT) { solAssert(registerTests( diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp new file mode 100644 index 00000000..e60f718d --- /dev/null +++ b/test/libyul/ObjectCompilerTest.cpp @@ -0,0 +1,134 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <test/libyul/ObjectCompilerTest.h> + +#include <test/libsolidity/FormattedScope.h> + +#include <libsolidity/interface/AssemblyStack.h> + +#include <libevmasm/Instruction.h> + +#include <liblangutil/SourceReferenceFormatter.h> + +#include <boost/algorithm/string.hpp> + +#include <fstream> + +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace yul::test; +using namespace dev::solidity; +using namespace dev::solidity::test; +using namespace std; + +ObjectCompilerTest::ObjectCompilerTest(string const& _filename) +{ + boost::filesystem::path path(_filename); + + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + string line; + while (getline(file, line)) + { + if (boost::algorithm::starts_with(line, "// ----")) + break; + if (m_source.empty() && boost::algorithm::starts_with(line, "// optimize")) + m_optimize = true; + m_source += line + "\n"; + } + while (getline(file, line)) + if (boost::algorithm::starts_with(line, "//")) + m_expectation += line.substr((line.size() >= 3 && line[2] == ' ') ? 3 : 2) + "\n"; + else + m_expectation += line + "\n"; +} + +bool ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + AssemblyStack stack(EVMVersion(), AssemblyStack::Language::StrictAssembly); + if (!stack.parseAndAnalyze("source", m_source)) + { + FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + printErrors(_stream, stack.errors()); + return false; + } + if (m_optimize) + stack.optimize(); + + MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM); + solAssert(obj.bytecode, ""); + + m_obtainedResult = "Assembly:\n" + obj.assembly; + if (obj.bytecode->bytecode.empty()) + m_obtainedResult += "-- empty bytecode --\n"; + else + m_obtainedResult += + "Bytecode: " + + toHex(obj.bytecode->bytecode) + + "\nOpcodes: " + + boost::trim_copy(solidity::disassemble(obj.bytecode->bytecode)) + + "\n"; + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + printIndented(_stream, m_expectation, nextIndentLevel); + FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; + printIndented(_stream, m_obtainedResult, nextIndentLevel); + return false; + } + return true; +} + +void ObjectCompilerTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void ObjectCompilerTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void ObjectCompilerTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + if (line.empty()) + // Avoid trailing spaces. + _stream << boost::trim_right_copy(_linePrefix) << endl; + else + _stream << _linePrefix << line << endl; +} + +void ObjectCompilerTest::printErrors(ostream& _stream, ErrorList const& _errors) +{ + SourceReferenceFormatter formatter(_stream); + + for (auto const& error: _errors) + formatter.printExceptionInformation( + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error" + ); +} diff --git a/test/libyul/ObjectCompilerTest.h b/test/libyul/ObjectCompilerTest.h new file mode 100644 index 00000000..a5f8d777 --- /dev/null +++ b/test/libyul/ObjectCompilerTest.h @@ -0,0 +1,69 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <test/TestCase.h> + +namespace langutil +{ +class Scanner; +class Error; +using ErrorList = std::vector<std::shared_ptr<Error const>>; +} + +namespace yul +{ +struct AsmAnalysisInfo; +struct Block; +} + +namespace yul +{ +namespace test +{ + +class ObjectCompilerTest: public dev::solidity::test::TestCase +{ +public: + static std::unique_ptr<TestCase> create(std::string const& _filename) + { + return std::unique_ptr<TestCase>(new ObjectCompilerTest(_filename)); + } + + explicit ObjectCompilerTest(std::string const& _filename); + + bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +private: + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); + void disambiguate(); + + static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); + + std::string m_source; + bool m_optimize = false; + std::string m_expectation; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/objectCompiler/data.yul b/test/libyul/objectCompiler/data.yul new file mode 100644 index 00000000..daa22d21 --- /dev/null +++ b/test/libyul/objectCompiler/data.yul @@ -0,0 +1,11 @@ +object "a" { + code {} + // Unreferenced data is not added to the assembled bytecode. + data "str" "Hello, World!" +} +// ---- +// Assembly: +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// Bytecode: fe +// Opcodes: INVALID diff --git a/test/libyul/objectCompiler/namedObject.yul b/test/libyul/objectCompiler/namedObject.yul new file mode 100644 index 00000000..940160fd --- /dev/null +++ b/test/libyul/objectCompiler/namedObject.yul @@ -0,0 +1,6 @@ +object "a" { + code {} +} +// ---- +// Assembly: +// -- empty bytecode -- diff --git a/test/libyul/objectCompiler/namedObjectCode.yul b/test/libyul/objectCompiler/namedObjectCode.yul new file mode 100644 index 00000000..4fc6891c --- /dev/null +++ b/test/libyul/objectCompiler/namedObjectCode.yul @@ -0,0 +1,13 @@ +object "a" { + code { sstore(0, 1) } +} +// ---- +// Assembly: +// /* "source":32:33 */ +// 0x01 +// /* "source":29:30 */ +// 0x00 +// /* "source":22:34 */ +// sstore +// Bytecode: 6001600055 +// Opcodes: PUSH1 0x1 PUSH1 0x0 SSTORE diff --git a/test/libyul/objectCompiler/nested_optimizer.yul b/test/libyul/objectCompiler/nested_optimizer.yul new file mode 100644 index 00000000..7739ce61 --- /dev/null +++ b/test/libyul/objectCompiler/nested_optimizer.yul @@ -0,0 +1,49 @@ +// optimize +object "a" { + code { + let x := calldataload(0) + let y := calldataload(0) + let z := sub(y, x) + sstore(add(x, 0), z) + } + object "sub" { + code { + let x := calldataload(0) + let y := calldataload(0) + let z := sub(y, x) + sstore(add(x, 0), z) + } + } +} +// ---- +// Assembly: +// /* "source":60:61 */ +// 0x00 +// /* "source":137:138 */ +// dup1 +// /* "source":60:61 */ +// dup2 +// /* "source":47:62 */ +// calldataload +// /* "source":119:139 */ +// sstore +// /* "source":32:143 */ +// pop +// stop +// +// sub_0: assembly { +// /* "source":200:201 */ +// 0x00 +// /* "source":283:284 */ +// dup1 +// /* "source":200:201 */ +// dup2 +// /* "source":187:202 */ +// calldataload +// /* "source":265:285 */ +// sstore +// /* "source":170:291 */ +// pop +// } +// Bytecode: 60008081355550fe +// Opcodes: PUSH1 0x0 DUP1 DUP2 CALLDATALOAD SSTORE POP INVALID diff --git a/test/libyul/objectCompiler/simple.yul b/test/libyul/objectCompiler/simple.yul new file mode 100644 index 00000000..d41b527c --- /dev/null +++ b/test/libyul/objectCompiler/simple.yul @@ -0,0 +1,13 @@ +{ + sstore(0, 1) +} +// ---- +// Assembly: +// /* "source":14:15 */ +// 0x01 +// /* "source":11:12 */ +// 0x00 +// /* "source":4:16 */ +// sstore +// Bytecode: 6001600055 +// Opcodes: PUSH1 0x1 PUSH1 0x0 SSTORE diff --git a/test/libyul/objectCompiler/simple_optimizer.yul b/test/libyul/objectCompiler/simple_optimizer.yul new file mode 100644 index 00000000..43b33553 --- /dev/null +++ b/test/libyul/objectCompiler/simple_optimizer.yul @@ -0,0 +1,23 @@ +// optimize +{ + let x := calldataload(0) + let y := calldataload(0) + let z := sub(y, x) + sstore(add(x, 0), z) +} +// ---- +// Assembly: +// /* "source":38:39 */ +// 0x00 +// /* "source":109:110 */ +// dup1 +// /* "source":38:39 */ +// dup2 +// /* "source":25:40 */ +// calldataload +// /* "source":91:111 */ +// sstore +// /* "source":12:113 */ +// pop +// Bytecode: 60008081355550 +// Opcodes: PUSH1 0x0 DUP1 DUP2 CALLDATALOAD SSTORE POP diff --git a/test/libyul/objectCompiler/smoke.yul b/test/libyul/objectCompiler/smoke.yul new file mode 100644 index 00000000..b2e44d4d --- /dev/null +++ b/test/libyul/objectCompiler/smoke.yul @@ -0,0 +1,5 @@ +{ +} +// ---- +// Assembly: +// -- empty bytecode -- diff --git a/test/libyul/objectCompiler/subObject.yul b/test/libyul/objectCompiler/subObject.yul new file mode 100644 index 00000000..98ea4d07 --- /dev/null +++ b/test/libyul/objectCompiler/subObject.yul @@ -0,0 +1,21 @@ +object "a" { + code {} + // Unreferenced data is not added to the assembled bytecode. + data "str" "Hello, World!" + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Assembly: +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// +// sub_0: assembly { +// /* "source":149:150 */ +// 0x01 +// /* "source":146:147 */ +// 0x00 +// /* "source":139:151 */ +// sstore +// } +// Bytecode: fe +// Opcodes: INVALID diff --git a/test/libyul/objectCompiler/subSubObject.yul b/test/libyul/objectCompiler/subSubObject.yul new file mode 100644 index 00000000..5e01f6dd --- /dev/null +++ b/test/libyul/objectCompiler/subSubObject.yul @@ -0,0 +1,39 @@ +object "a" { + code {} + // Unreferenced data is not added to the assembled bytecode. + data "str" "Hello, World!" + object "sub" { + code { sstore(0, 1) } + object "subsub" { + code { sstore(2, 3) } + data "str" hex"123456" + } + } +} +// ---- +// Assembly: +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// +// sub_0: assembly { +// /* "source":153:154 */ +// 0x01 +// /* "source":150:151 */ +// 0x00 +// /* "source":143:155 */ +// sstore +// stop +// +// sub_0: assembly { +// /* "source":203:204 */ +// 0x03 +// /* "source":200:201 */ +// 0x02 +// /* "source":193:205 */ +// sstore +// stop +// data_6adf031833174bbe4c85eafe59ddb54e6584648c2c962c6f94791ab49caa0ad4 123456 +// } +// } +// Bytecode: fe +// Opcodes: INVALID diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 736212fc..da8e0b39 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -4,7 +4,19 @@ target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_L add_executable(yulopti yulopti.cpp) target_link_libraries(yulopti PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) -add_executable(isoltest isoltest.cpp ../Options.cpp ../Common.cpp ../TestCase.cpp ../libsolidity/SyntaxTest.cpp - ../libsolidity/AnalysisFramework.cpp ../libsolidity/SolidityExecutionFramework.cpp ../ExecutionFramework.cpp - ../RPCSession.cpp ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerJSONTest.cpp ../libyul/YulOptimizerTest.cpp) +add_executable(isoltest + isoltest.cpp + ../Options.cpp + ../Common.cpp + ../TestCase.cpp + ../libsolidity/SyntaxTest.cpp + ../libsolidity/AnalysisFramework.cpp + ../libsolidity/SolidityExecutionFramework.cpp + ../ExecutionFramework.cpp + ../RPCSession.cpp + ../libsolidity/ASTJSONTest.cpp + ../libsolidity/SMTCheckerJSONTest.cpp + ../libyul/ObjectCompilerTest.cpp + ../libyul/YulOptimizerTest.cpp +) target_link_libraries(isoltest PRIVATE libsolc solidity evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index f8e2dc58..13585887 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -23,6 +23,7 @@ #include <test/libsolidity/ASTJSONTest.h> #include <test/libsolidity/SMTCheckerJSONTest.h> #include <test/libyul/YulOptimizerTest.h> +#include <test/libyul/ObjectCompilerTest.h> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/replace.hpp> @@ -401,6 +402,17 @@ Allowed options)", else return 1; + if (auto stats = runTestSuite( + "Yul Object Compiler", + testPath / "libyul", + "objectCompiler", + yul::test::ObjectCompilerTest::create, + formatted + )) + global_stats += *stats; + else + return 1; + if (!disableSMT) { if (auto stats = runTestSuite( |