diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index bd81f7c0b..32ea08dbe 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -111,7 +111,10 @@ void OptimiserSuite::run( )(*_object.code)); Block& ast = *_object.code; - OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast, _expectedExecutionsPerDeployment); + NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; + OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; + + OptimiserSuite suite(context, Debug::None); // Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. @@ -162,7 +165,7 @@ void OptimiserSuite::run( ast.statements.erase(ast.statements.begin()); } - suite.m_dispenser.reset(ast); + dispenser.reset(ast); NameSimplifier::run(suite.m_context, ast); VarNameCleaner::run(suite.m_context, ast); diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 9b38ce542..3c85fc559 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -59,6 +59,8 @@ public: PrintStep, PrintChanges }; + OptimiserSuite(OptimiserStepContext& _context, Debug _debug = Debug::None): m_context(_context), m_debug(_debug) {} + /// The value nullopt for `_expectedExecutionsPerDeployment` represents creation code. static void run( Dialect const& _dialect, @@ -82,20 +84,7 @@ public: static std::map const& stepAbbreviationToNameMap(); private: - OptimiserSuite( - Dialect const& _dialect, - std::set const& _externallyUsedIdentifiers, - Debug _debug, - Block& _ast, - std::optional expectedExecutionsPerDeployment - ): - m_dispenser{_dialect, _ast, _externallyUsedIdentifiers}, - m_context{_dialect, m_dispenser, _externallyUsedIdentifiers, expectedExecutionsPerDeployment}, - m_debug(_debug) - {} - - NameDispenser m_dispenser; - OptimiserStepContext m_context; + OptimiserStepContext& m_context; Debug m_debug; }; diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 752aaafa6..5c4f00f0a 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -76,58 +76,66 @@ namespace po = boost::program_options; class YulOpti { public: - void printErrors() + static void printErrors(CharStream const& _charStream, ErrorList const& _errors) { SourceReferenceFormatter{ cerr, - SingletonCharStreamProvider(*m_charStream), + SingletonCharStreamProvider(_charStream), true, false - }.printErrorInformation(m_errors); + }.printErrorInformation(_errors); } - bool parse(string const& _input) + void parse(string const& _input) { - ErrorReporter errorReporter(m_errors); - m_charStream = make_shared(_input, ""); - m_ast = yul::Parser(errorReporter, m_dialect).parse(*m_charStream); - if (!m_ast || !errorReporter.errors().empty()) + ErrorList errors; + ErrorReporter errorReporter(errors); + CharStream _charStream(_input, ""); + try { - cerr << "Error parsing source." << endl; - printErrors(); - return false; + m_ast = yul::Parser(errorReporter, m_dialect).parse(_charStream); + if (!m_ast || !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."); + } } - m_analysisInfo = make_shared(); - AsmAnalyzer analyzer( - *m_analysisInfo, - errorReporter, - m_dialect - ); - if (!analyzer.analyze(*m_ast) || !errorReporter.errors().empty()) + catch(...) { - cerr << "Error analyzing source." << endl; - printErrors(); - return false; + cerr << "Fatal error during parsing: " << endl; + printErrors(_charStream, errors); + throw; } - return true; } void printUsageBanner( - map const& _optimizationSteps, map const& _extraOptions, size_t _columns ) { 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( - max_element(_optimizationSteps.begin(), _optimizationSteps.end(), hasShorterString)->second.size(), + max_element(optimiserSteps.begin(), optimiserSteps.end(), hasShorterString)->second.size(), max_element(_extraOptions.begin(), _extraOptions.end(), hasShorterString)->second.size() ); vector overlappingAbbreviations = - ranges::views::set_intersection(_extraOptions | ranges::views::keys, _optimizationSteps | ranges::views::keys) | + ranges::views::set_intersection(_extraOptions | ranges::views::keys, optimiserSteps | ranges::views::keys) | ranges::views::transform([](char _abbreviation){ return string(1, _abbreviation); }) | ranges::to(); @@ -141,7 +149,7 @@ public: ); vector> sortedOptions = - ranges::views::concat(_optimizationSteps, _extraOptions) | + ranges::views::concat(optimiserSteps, _extraOptions) | ranges::to>>() | ranges::actions::sort([](tuple const& _a, tuple const& _b) { return ( @@ -162,24 +170,28 @@ public: } } - void runInteractive(string source) + void disambiguate() { - bool disambiguated = false; + *m_ast = std::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); + m_analysisInfo.reset(); + m_nameDispenser.reset(*m_ast); + } + + void runSteps(string _source, string _steps) + { + parse(_source); + disambiguate(); + OptimiserSuite{m_context}.runSequence(_steps, *m_ast); + cout << AsmPrinter{m_dialect}(*m_ast) << endl; + } + + void runInteractive(string _source, bool _disambiguated = false) + { + bool disambiguated = _disambiguated; while (true) { - cout << "----------------------" << endl; - cout << source << endl; - if (!parse(source)) - return; - set reservedIdentifiers; - if (!disambiguated) - { - *m_ast = std::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); - m_analysisInfo.reset(); - m_nameDispenser = make_shared(m_dialect, *m_ast, reservedIdentifiers); - disambiguated = true; - } - map const& abbreviationMap = OptimiserSuite::stepAbbreviationToNameMap(); + 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 <<<"}, @@ -187,99 +199,159 @@ public: {';', "StackCompressor"} }; - printUsageBanner(abbreviationMap, extraOptions, 4); + printUsageBanner(extraOptions, 4); cout << "? "; cout.flush(); - // TODO: handle EOF properly. char option = static_cast(readStandardInputChar()); cout << ' ' << option << endl; - OptimiserStepContext context{ - m_dialect, - *m_nameDispenser, - reservedIdentifiers, - solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment - }; - - auto abbreviationAndName = abbreviationMap.find(option); - if (abbreviationAndName != abbreviationMap.end()) + try { - OptimiserStep const& step = *OptimiserSuite::allSteps().at(abbreviationAndName->second); - step.run(context, *m_ast); + switch (option) + { + case 4: + case '#': + return; + case ',': + VarNameCleaner::run(m_context, *m_ast); + // 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); + break; + } + default: + OptimiserSuite{m_context}.runSequence( + std::string_view(&option, 1), + *m_ast + ); + } + _source = AsmPrinter{m_dialect}(*m_ast); } - else switch (option) + catch (...) { - case '#': - return; - case ',': - VarNameCleaner::run(context, *m_ast); - // 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); - break; + cerr << endl << "Exception during optimiser step:" << endl; + cerr << boost::current_exception_diagnostic_information() << endl; } - default: - cerr << "Unknown option." << endl; - } - source = AsmPrinter{m_dialect}(*m_ast); + cout << "----------------------" << endl; + cout << _source << endl; } } private: - ErrorList m_errors; - shared_ptr m_charStream; shared_ptr m_ast; Dialect const& m_dialect{EVMDialect::strictAssemblyForEVMObjects(EVMVersion{})}; - shared_ptr m_analysisInfo; - shared_ptr m_nameDispenser; + unique_ptr m_analysisInfo; + set const m_reservedIdentifiers = {}; + NameDispenser m_nameDispenser{m_dialect, m_reservedIdentifiers}; + OptimiserStepContext m_context{ + m_dialect, + m_nameDispenser, + m_reservedIdentifiers, + solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment + }; }; 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, -interactively read from stdin. - -Allowed options)", - po::options_description::m_default_line_length, - po::options_description::m_default_line_length - 23); - options.add_options() - ( - "input-file", - po::value(), - "input file" - ) - ("help", "Show this help screen."); - - // All positional options should be interpreted as input files - po::positional_options_description filesPositions; - filesPositions.add("input-file", 1); - - po::variables_map arguments; try { + bool nonInteractive = false; + po::options_description options( + R"(yulopti, yul optimizer exploration tool. + Usage: yulopti [Options] + Reads as yul code 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. + + Allowed options)", + po::options_description::m_default_line_length, + po::options_description::m_default_line_length - 23); + options.add_options() + ( + "input-file", + po::value(), + "input file" + ) + ( + "steps", + po::value(), + "steps to execute non-interactively" + ) + ( + "non-interactive,n", + po::bool_switch(&nonInteractive)->default_value(false), + "stop after executing the provided steps" + ) + ("help,h", "Show this help screen."); + + // All positional options should be interpreted as input files + po::positional_options_description filesPositions; + filesPositions.add("input-file", 1); + + po::variables_map arguments; po::command_line_parser cmdLineParser(argc, argv); cmdLineParser.options(options).positional(filesPositions); po::store(cmdLineParser.run(), arguments); + po::notify(arguments); + + if (arguments.count("help")) + { + cout << options; + return 0; + } + + string input; + if (arguments.count("input-file")) + { + string filename = arguments["input-file"].as(); + if (filename == "-") + { + nonInteractive = true; + input = readUntilEnd(cin); + } + else + input = readFileAsString(arguments["input-file"].as()); + } + else + { + cout << options; + return 1; + } + + if (nonInteractive && !arguments.count("steps")) + { + cout << options; + return 1; + } + + YulOpti yulOpti; + bool disambiguated = false; + if (!nonInteractive) + cout << input << endl; + if (arguments.count("steps")) + { + string sequence = arguments["steps"].as(); + if (!nonInteractive) + cout << "----------------------" << endl; + yulOpti.runSteps(input, sequence); + disambiguated = true; + } + if (!nonInteractive) + yulOpti.runInteractive(input, disambiguated); + + return 0; } catch (po::error const& _exception) { cerr << _exception.what() << endl; return 1; } - - string input; - try - { - input = readFileAsString(arguments["input-file"].as()); - } catch (FileNotFound const& _exception) { cerr << "File not found:" << _exception.comment() << endl; @@ -290,11 +362,10 @@ Allowed options)", cerr << "Not a regular file:" << _exception.comment() << endl; return 1; } - - if (arguments.count("input-file")) - YulOpti{}.runInteractive(input); - else - cout << options; - - return 0; + catch(...) + { + cerr << endl << "Exception:" << endl; + cerr << boost::current_exception_diagnostic_information() << endl; + return 1; + } }