diff --git a/libyul/Object.cpp b/libyul/Object.cpp index 83ee66a3b..12b964e80 100644 --- a/libyul/Object.cpp +++ b/libyul/Object.cpp @@ -52,6 +52,12 @@ std::string indent(std::string const& _input) } +Object const* Object::at(YulString _name) const +{ + size_t subIndex = this->subIndexByName.at(_name); + return dynamic_cast(this->subObjects[subIndex].get()); +} + std::string Data::toString(Dialect const*, DebugInfoSelection const&, CharStreamProvider const*) const { return "data \"" + name.str() + "\" hex\"" + util::toHex(data) + "\""; @@ -143,8 +149,32 @@ std::set Object::qualifiedDataNames() const return qualifiedNames; } +void Object::visitPath(YulString _qualifiedName, std::function const& _visitor) const +{ + yulAssert(!_qualifiedName.empty(), ""); + + std::vector subObjectPathComponents; + boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of(".")); + + Object const* object = this; + for (std::string const& currentSubObjectName: subObjectPathComponents) + { + yulAssert(!currentSubObjectName.empty(), ""); + + YulString subObjectName = YulString{currentSubObjectName}; + yulAssert( + object->subIndexByName.count(subObjectName), + "Assembly object not found or does not contain code." + ); + object = object->at(subObjectName); + if (object && _visitor(object)) + break; + } +} + std::vector Object::pathToSubObject(YulString _qualifiedName) const { + std::vector path; yulAssert(_qualifiedName != name, ""); yulAssert(subIndexByName.count(name) == 0, ""); @@ -152,24 +182,34 @@ std::vector Object::pathToSubObject(YulString _qualifiedName) const _qualifiedName = YulString{_qualifiedName.str().substr(name.str().length() + 1)}; yulAssert(!_qualifiedName.empty(), ""); - std::vector subObjectPathComponents; - boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of(".")); - - std::vector path; - Object const* object = this; - for (std::string const& currentSubObjectName: subObjectPathComponents) - { - yulAssert(!currentSubObjectName.empty(), ""); - auto subIndexIt = object->subIndexByName.find(YulString{currentSubObjectName}); - yulAssert( - subIndexIt != object->subIndexByName.end(), - "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code." - ); - object = dynamic_cast(object->subObjects[subIndexIt->second].get()); - yulAssert(object, "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code."); - yulAssert(object->subId != std::numeric_limits::max(), ""); - path.push_back({object->subId}); - } - + this->visitPath(_qualifiedName, [&](Object const* _object) -> bool { + yulAssert(_object->subId != std::numeric_limits::max(), ""); + path.push_back(_object->subId); + return false; + }); return path; } + +Object const* Object::subObjectAt(YulString _qualifiedName) +{ + if (_qualifiedName.empty()) + return nullptr; + + // If there is no `.` in the given `_qualifiedName`, the target + // object name is considered to be the `_qualifiedName`, otherwise, + // the target object name is the last element in the path given by `_qualifiedName`. + YulString targetObjectName = _qualifiedName; + size_t targetObjectPos = _qualifiedName.str().find_last_of("."); + if (targetObjectPos != std::string::npos) + targetObjectName = YulString(_qualifiedName.str().substr(targetObjectPos + 1)); + + Object const* foundObject = nullptr; + this->visitPath(_qualifiedName, [&](Object const* _subObject) -> bool { + if (targetObjectName != _subObject->name) + return false; + foundObject = _subObject; + return true; + }); + + return foundObject; +} diff --git a/libyul/Object.h b/libyul/Object.h index 5f88d3bc7..4a97870a5 100644 --- a/libyul/Object.h +++ b/libyul/Object.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -116,6 +117,21 @@ public: /// The path must not lead to a @a Data object (will throw in that case). std::vector pathToSubObject(YulString _qualifiedName) const; + /// Searches for a subobject at @param _qualifiedName within the current object. + /// @returns a pointer to the subobject or a nullptr if it was not found. + Object const* subObjectAt(YulString _qualifiedName); + + /// Visits all subobjects in the path given by the @a _qualifiedName + /// of the current object applying the function @a _visitor. + /// If @a _visitor returns `true` the visiting stops, otherwise, + /// it continues until the last subobject in the path is reached. + void visitPath( + YulString _qualifiedName, + std::function const& _visitor + ) const; + + Object const* at(YulString _name) const; + /// sub id for object if it is subobject of another object, max value if it is not subobject size_t subId = std::numeric_limits::max(); diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 20491afe1..300d9d940 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -90,33 +91,29 @@ public: { ErrorList errors; ErrorReporter errorReporter(errors); - CharStream _charStream(_input, ""); + 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); + m_inputIsCodeBlock = (scanner->currentToken() == Token::LBrace); + m_object = parser.parse(scanner, false); + + 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."); + printErrors(charStream, errors); + solThrow(Exception, "Could not parse source."); } + + runCodeAnalyzer(errorReporter); } catch(...) { cerr << "Fatal error during parsing: " << endl; - printErrors(_charStream, errors); + printErrors(charStream, errors); throw; } } @@ -129,7 +126,7 @@ public: yulAssert(_columns > 0); auto const& optimiserSteps = OptimiserSuite::stepAbbreviationToNameMap(); auto hasShorterString = [](auto const& a, auto const& b) { return a.second.size() < b.second.size(); }; - size_t longestDescriptionLength = std::max( + size_t longestDescriptionLength = max( max_element(optimiserSteps.begin(), optimiserSteps.end(), hasShorterString)->second.size(), max_element(_extraOptions.begin(), _extraOptions.end(), hasShorterString)->second.size() ); @@ -170,28 +167,152 @@ public: } } - void disambiguate() + void applyFunctionToObjectAndSubobjects(Object& _object, function _function) { - *m_ast = std::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); - m_analysisInfo.reset(); - m_nameDispenser.reset(*m_ast); + for (auto const& subObjectNode: _object.subObjects) + { + auto subObject = dynamic_pointer_cast(subObjectNode); + + if (subObject != nullptr) + applyFunctionToObjectAndSubobjects(*subObject, _function); + } + + _function(_object); } - void runSteps(string _source, string _steps) + void runCodeAnalyzer(ErrorReporter& _errorReporter) + { + applyFunctionToObjectAndSubobjects( + *m_object, + [&](Object& _object) + { + _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 runCodeDisambiguator() + { + applyFunctionToObjectAndSubobjects( + *m_object, + [&](Object& _object) + { + _object.code = make_shared( + get(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code)) + ); + + _object.analysisInfo.reset(); + } + ); + } + + void runSequence(string_view _steps) + { + applyFunctionToObjectAndSubobjects( + *m_object, + [&](Object& _object) + { + OptimiserStepContext context = createOptimiserStepContext(_object); + OptimiserSuite{context}.runSequence(_steps, *_object.code); + } + ); + } + + void runVarNameCleaner() + { + applyFunctionToObjectAndSubobjects( + *m_object, + [&](Object& _object) + { + OptimiserStepContext context = createOptimiserStepContext(_object); + VarNameCleaner::run(context, *_object.code); + } + ); + } + + void runStackCompressor() + { + applyFunctionToObjectAndSubobjects( + *m_object, + [&](Object& _object) { StackCompressor::run(m_dialect, _object, true, 16); } + ); + } + + void printObject(string const& _objectPath) + { + if (!m_inputIsCodeBlock) + { + Object const* subObject = m_object->subObjectAt(YulString(_objectPath)); + + if (subObject == nullptr) + solThrow(Exception, "Assembly object not found."); + + cout << subObject->toString(&m_dialect) << endl; + } + else + { + yulAssert( + m_object->subObjects.empty(), + "Unexpected subObjects found." + ); + + if (!_objectPath.empty()) + solThrow(Exception, "Object path argument cannot be used. Input is a code block."); + + cout << AsmPrinter{m_dialect}(*m_object->code) << endl; + } + } + + void resetNameDispenser() + { + m_nameDispenser = make_shared( + m_dialect, + m_reservedIdentifiers + ); + } + + void parseAndRunSteps(string const& _source, string const& _steps) { parse(_source); - disambiguate(); - OptimiserSuite{m_context}.runSequence(_steps, *m_ast); - cout << AsmPrinter{m_dialect}(*m_ast) << endl; + runCodeDisambiguator(); + runSequence(_steps); } - void runInteractive(string _source, bool _disambiguated = false) + OptimiserStepContext createOptimiserStepContext(Object& _object) { + bool isCreation = m_object.get() == &_object || !boost::ends_with(_object.name.str(), "_deployed"); + + return OptimiserStepContext{ + m_dialect, + *m_nameDispenser, + m_reservedIdentifiers, + isCreation ? nullopt : make_optional(OptimiserSettings::standard().expectedExecutionsPerDeployment) + }; + } + + void runInteractive(string _source, string const& _objectPath, bool _disambiguated = false) + { + ErrorList errors; + ErrorReporter errorReporter(errors); bool disambiguated = _disambiguated; + + parse(_source); + while (true) { - parse(_source); - disambiguated = disambiguated || (disambiguate(), true); + disambiguated = disambiguated || (runCodeDisambiguator(), 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 +334,18 @@ 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(string_view(&option, 1)); } - _source = AsmPrinter{m_dialect}(*m_ast); + + resetNameDispenser(); } catch (...) { @@ -238,22 +353,20 @@ public: cerr << boost::current_exception_diagnostic_information() << endl; } cout << "----------------------" << endl; - cout << _source << endl; + printObject(_objectPath); } } private: - shared_ptr m_ast; + shared_ptr m_object; + bool m_inputIsCodeBlock = false; + 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 + ); }; int main(int argc, char** argv) @@ -264,7 +377,7 @@ 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 either a yul object or a yul code block 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. @@ -283,6 +396,14 @@ int main(int argc, char** argv) po::value(), "steps to execute non-interactively" ) + ( + "object", + po::value()->value_name("path"), + "Dotted path to a specific Yul object in the source to print after running the steps. " + "If not specified, the top-level object will be selected. " + "The object will be printed recursively. " + "Only valid if the source actually contains objects (rather than a block of Yul code)." + ) ( "non-interactive,n", po::bool_switch(&nonInteractive)->default_value(false), @@ -307,6 +428,8 @@ int main(int argc, char** argv) } string input; + string objectPath; + if (arguments.count("input-file")) { string filename = arguments["input-file"].as(); @@ -330,20 +453,28 @@ 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.parse(input); + yulOpti.printObject(objectPath); + } + if (arguments.count("steps")) { string sequence = arguments["steps"].as(); if (!nonInteractive) cout << "----------------------" << endl; - yulOpti.runSteps(input, sequence); + yulOpti.parseAndRunSteps(input, sequence); disambiguated = true; } if (!nonInteractive) - yulOpti.runInteractive(input, disambiguated); + yulOpti.runInteractive(input, objectPath, disambiguated); return 0; }