From 3078ab2d9c060d27e917d055df8fe5d660e5f93b Mon Sep 17 00:00:00 2001 From: Daniel Lupu Date: Sun, 17 Apr 2022 18:11:23 +0300 Subject: [PATCH] yulopti: Add support for Yul Objects --- libyul/ObjectParser.cpp | 3 + test/tools/yulopti.cpp | 209 +++++++++++++++++++++++++++++++--------- 2 files changed, 166 insertions(+), 46 deletions(-) diff --git a/libyul/ObjectParser.cpp b/libyul/ObjectParser.cpp index 2038959de..a511e33f3 100644 --- a/libyul/ObjectParser.cpp +++ b/libyul/ObjectParser.cpp @@ -99,7 +99,10 @@ std::shared_ptr ObjectParser::parseObject(Object* _containingObject) fatalParserError(8143_error, "Expected keyword \"data\" or \"object\" or \"}\"."); } if (_containingObject) + { + ret->subId = _containingObject->subObjects.size(); addNamedSubObject(*_containingObject, ret->name, ret); + } expectToken(Token::RBrace); diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 20491afe1..b17cd1f5d 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -86,32 +87,44 @@ public: }.printErrorInformation(_errors); } - void parse(string const& _input) + shared_ptr getSubObject(shared_ptr const& _rootObject, string const& _path) + { + if (_path.empty()) + return _rootObject; + + auto pathToSubObject = _rootObject->pathToSubObject(YulString(_path)); + auto subObject = _rootObject; + + for (auto const& i: pathToSubObject) + subObject = dynamic_pointer_cast(subObject->subObjects[i]); + + return subObject; + } + + void parse(string const& _input, string const& _objectPath) { ErrorList errors; ErrorReporter errorReporter(errors); CharStream _charStream(_input, ""); + try { - m_ast = yul::Parser(errorReporter, m_dialect).parse(_charStream); - if (!m_ast || !errorReporter.errors().empty()) + ObjectParser parser(errorReporter, m_dialect); + + auto scanner = make_shared(_charStream); + auto content = parser.parse(scanner, false); + + if (content != nullptr) + m_object = getSubObject(content, _objectPath); + + if (!m_object || !errorReporter.errors().empty()) { cerr << "Error parsing source." << endl; printErrors(_charStream, errors); throw std::runtime_error("Could not parse source."); } - m_analysisInfo = make_unique(); - AsmAnalyzer analyzer( - *m_analysisInfo, - errorReporter, - m_dialect - ); - if (!analyzer.analyze(*m_ast) || !errorReporter.errors().empty()) - { - cerr << "Error analyzing source." << endl; - printErrors(_charStream, errors); - throw std::runtime_error("Could not analyze source."); - } + + analyze(errorReporter); } catch(...) { @@ -170,28 +183,118 @@ public: } } + void objectApply(function _fn) + { + vector> stack; + stack.push_back(m_object); + + while (!stack.empty()) { + auto object = stack.back(); + stack.pop_back(); + + for (auto const& subObjectNode: object->subObjects) { + auto subObject = dynamic_pointer_cast(subObjectNode); + + if (subObject != nullptr) + stack.push_back(subObject); + } + + _fn(*object); + } + } + + void analyze(ErrorReporter& errorReporter) + { + objectApply([&](Object& object) -> void { + object.analysisInfo = make_shared(); + + AsmAnalyzer analyzer( + *object.analysisInfo, + errorReporter, + m_dialect, + {}, + object.qualifiedDataNames() + ); + + bool success = analyzer.analyze(*object.code); + yulAssert(success && !errorReporter.hasErrors(), "Invalid assembly/yul code."); + }); + } + void disambiguate() { - *m_ast = std::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); - m_analysisInfo.reset(); - m_nameDispenser.reset(*m_ast); + objectApply([&](Object& object) -> void { + object.code = make_shared( + std::get(Disambiguator(m_dialect, *object.analysisInfo)(*object.code)) + ); + + object.analysisInfo.reset(); + }); } - void runSteps(string _source, string _steps) + void runSequence(string_view _steps) { - parse(_source); + objectApply([&](Object& object) -> void { + OptimiserSuite{*m_context}.runSequence(_steps, *object.code); + }); + } + + void runVarNameCleaner() + { + objectApply([&](Object& object) -> void { + VarNameCleaner::run(*m_context, *object.code); + }); + } + + void runStackCompressor() + { + objectApply([&](Object& object) -> void { + StackCompressor::run(m_dialect, object, true, 16); + }); + } + + void parseAndPrint(string _source, string _objectPath) + { + parse(_source, _objectPath); + cout << m_object->toString(&m_dialect) << endl; + } + + void resetNameDispenser() + { + m_nameDispenser = make_shared( + m_dialect, + m_reservedIdentifiers + ); + + m_context = make_shared( + OptimiserStepContext{ + m_dialect, + *m_nameDispenser, + m_reservedIdentifiers, + solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment + } + ); + } + + void runSteps(string _source, string _objectPath, string _steps) + { + parse(_source, _objectPath); disambiguate(); - OptimiserSuite{m_context}.runSequence(_steps, *m_ast); - cout << AsmPrinter{m_dialect}(*m_ast) << endl; + runSequence(_steps); } - void runInteractive(string _source, bool _disambiguated = false) + void runInteractive(string _source, string const& _objectPath, bool _disambiguated = false) { + ErrorList errors; + ErrorReporter errorReporter(errors); bool disambiguated = _disambiguated; + + parse(_source, _objectPath); + while (true) { - parse(_source); disambiguated = disambiguated || (disambiguate(), true); + map const& extraOptions = { // QUIT starts with a non-letter character on purpose to get it to show up on top of the list {'#', ">>> QUIT <<<"}, @@ -213,24 +316,21 @@ public: case '#': return; case ',': - VarNameCleaner::run(m_context, *m_ast); + runVarNameCleaner(); // VarNameCleaner destroys the unique names guarantee of the disambiguator. disambiguated = false; break; case ';': { - Object obj; - obj.code = m_ast; - StackCompressor::run(m_dialect, obj, true, 16); + runStackCompressor(); break; } default: - OptimiserSuite{m_context}.runSequence( - std::string_view(&option, 1), - *m_ast - ); + runSequence(std::string_view(&option, 1)); } - _source = AsmPrinter{m_dialect}(*m_ast); + + resetNameDispenser(); + analyze(errorReporter); } catch (...) { @@ -238,22 +338,26 @@ public: cerr << boost::current_exception_diagnostic_information() << endl; } cout << "----------------------" << endl; - cout << _source << endl; + cout << m_object->toString(&m_dialect) << endl; } } private: - shared_ptr m_ast; + shared_ptr m_object; Dialect const& m_dialect{EVMDialect::strictAssemblyForEVMObjects(EVMVersion{})}; - unique_ptr m_analysisInfo; set const m_reservedIdentifiers = {}; - NameDispenser m_nameDispenser{m_dialect, m_reservedIdentifiers}; - OptimiserStepContext m_context{ + shared_ptr m_nameDispenser = make_shared( m_dialect, - m_nameDispenser, - m_reservedIdentifiers, - solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment - }; + m_reservedIdentifiers + ); + shared_ptr m_context = make_shared( + OptimiserStepContext{ + m_dialect, + *m_nameDispenser, + m_reservedIdentifiers, + solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment + } + ); }; int main(int argc, char** argv) @@ -264,10 +368,11 @@ int main(int argc, char** argv) po::options_description options( R"(yulopti, yul optimizer exploration tool. Usage: yulopti [Options] - Reads as yul code and applies optimizer steps to it, + Reads containing a yul object and applies optimizer steps to it, interactively read from stdin. In non-interactive mode a list of steps has to be provided. If is -, yul code is read from stdin and run non-interactively. + If is provided then only the object matching the given path will be read. Allowed options)", po::options_description::m_default_line_length, @@ -283,6 +388,11 @@ int main(int argc, char** argv) po::value(), "steps to execute non-interactively" ) + ( + "object", + po::value(), + "path to a yul object in the input" + ) ( "non-interactive,n", po::bool_switch(&nonInteractive)->default_value(false), @@ -307,6 +417,8 @@ int main(int argc, char** argv) } string input; + string objectPath; + if (arguments.count("input-file")) { string filename = arguments["input-file"].as(); @@ -330,20 +442,25 @@ int main(int argc, char** argv) return 1; } + if (arguments.count("object")) + objectPath = arguments["object"].as(); + YulOpti yulOpti; bool disambiguated = false; + if (!nonInteractive) - cout << input << endl; + yulOpti.parseAndPrint(input, objectPath); + if (arguments.count("steps")) { string sequence = arguments["steps"].as(); if (!nonInteractive) cout << "----------------------" << endl; - yulOpti.runSteps(input, sequence); + yulOpti.runSteps(input, objectPath, sequence); disambiguated = true; } if (!nonInteractive) - yulOpti.runInteractive(input, disambiguated); + yulOpti.runInteractive(input, objectPath, disambiguated); return 0; }