diff options
-rw-r--r-- | libdevcore/CommonIO.cpp | 39 | ||||
-rw-r--r-- | libdevcore/CommonIO.h | 8 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 19 | ||||
-rw-r--r-- | libyul/optimiser/CommonSubexpressionEliminator.cpp | 5 | ||||
-rw-r--r-- | libyul/optimiser/DataFlowAnalyzer.cpp | 35 | ||||
-rw-r--r-- | libyul/optimiser/DataFlowAnalyzer.h | 8 | ||||
-rw-r--r-- | libyul/optimiser/FullInliner.cpp | 19 | ||||
-rw-r--r-- | libyul/optimiser/FullInliner.h | 4 | ||||
-rw-r--r-- | libyul/optimiser/Rematerialiser.cpp | 9 | ||||
-rw-r--r-- | solc/CommandLineInterface.cpp | 10 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 15 | ||||
-rw-r--r-- | test/libyul/YulOptimizerTest.cpp | 2 | ||||
-rw-r--r-- | test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul | 22 | ||||
-rw-r--r-- | test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul | 25 | ||||
-rw-r--r-- | test/libyul/yulOptimizerTests/fullSimplify/operations.yul | 44 | ||||
-rw-r--r-- | test/libyul/yulOptimizerTests/fullSimplify/signextend.yul | 12 |
16 files changed, 182 insertions, 94 deletions
diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 1aa3504c..cc730575 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -80,45 +80,6 @@ string dev::readStandardInput() return ret; } -void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename) -{ - namespace fs = boost::filesystem; - if (_writeDeleteRename) - { - fs::path tempPath = fs::unique_path(_file + "-%%%%%%"); - writeFile(tempPath.string(), _data, false); - // will delete _file if it exists - fs::rename(tempPath, _file); - } - else - { - // create directory if not existent - fs::path p(_file); - if (!p.parent_path().empty() && !fs::exists(p.parent_path())) - { - fs::create_directories(p.parent_path()); - try - { - fs::permissions(p.parent_path(), fs::owner_all); - } - catch (...) - { - } - } - - ofstream s(_file, ios::trunc | ios::binary); - s.write(reinterpret_cast<char const*>(_data.data()), _data.size()); - assertThrow(s, FileError, "Could not write to file: " + _file); - try - { - fs::permissions(_file, fs::owner_read|fs::owner_write); - } - catch (...) - { - } - } -} - #if defined(_WIN32) class DisableConsoleBuffering { diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 928b6d15..b9f941ea 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -41,14 +41,6 @@ std::string readStandardInput(); /// Retrieve and returns a character from standard input (without waiting for EOL). int readStandardInputChar(); -/// Write the given binary data into the given file, replacing the file if it pre-exists. -/// Throws exception on error. -/// @param _writeDeleteRename useful not to lose any data: If set, first writes to another file in -/// the same directory and then moves that file. -void writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename = false); -/// Write the given binary data into the given file, replacing the file if it pre-exists. -inline void writeFile(std::string const& _file, bytes const& _data, bool _writeDeleteRename = false) { writeFile(_file, bytesConstRef(&_data), _writeDeleteRename); } -inline void writeFile(std::string const& _file, std::string const& _data, bool _writeDeleteRename = false) { writeFile(_file, bytesConstRef(_data), _writeDeleteRename); } /// Converts arbitrary value to string representation using std::stringstream. template <class _T> std::string toString(_T const& _t) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 27440289..749739ce 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -568,12 +568,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::External: case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCall: - case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareStaticCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; + case FunctionType::Kind::BareCallCode: + solAssert(false, "Callcode has been removed."); case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); @@ -1328,8 +1329,6 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::CALLVALUE; else if (member == "origin") m_context << Instruction::ORIGIN; - else if (member == "gas") - m_context << Instruction::GAS; else if (member == "gasprice") m_context << Instruction::GASPRICE; else if (member == "data") @@ -1337,9 +1336,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "sig") m_context << u256(0) << Instruction::CALLDATALOAD << (u256(0xffffffff) << (256 - 32)) << Instruction::AND; + else if (member == "gas") + solAssert(false, "Gas has been removed."); else if (member == "blockhash") - { - } + solAssert(false, "Blockhash has been removed."); else solAssert(false, "Unknown magic member."); break; @@ -1844,8 +1844,9 @@ void ExpressionCompiler::appendExternalFunctionCall( solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); - bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; - bool isCallCode = funKind == FunctionType::Kind::BareCallCode; + solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); + + bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); @@ -1930,7 +1931,7 @@ void ExpressionCompiler::appendExternalFunctionCall( parameterTypes, _functionType.padArguments(), _functionType.takesArbitraryParameters() || _functionType.isBareCall(), - isCallCode || isDelegateCall + isDelegateCall ); // Stack now: @@ -2001,8 +2002,6 @@ void ExpressionCompiler::appendExternalFunctionCall( // Order is important here, STATICCALL might overlap with DELEGATECALL. if (isDelegateCall) m_context << Instruction::DELEGATECALL; - else if (isCallCode) - m_context << Instruction::CALLCODE; else if (useStaticCall) m_context << Instruction::STATICCALL; else diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index 23d15cad..51737097 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -50,8 +50,8 @@ void CommonSubexpressionEliminator::visit(Expression& _e) if (m_value.at(name)->type() == typeid(Identifier)) { string const& value = boost::get<Identifier>(*m_value.at(name)).name; - if (inScope(value)) - _e = Identifier{locationOf(_e), value}; + assertThrow(inScope(value), OptimizerException, ""); + _e = Identifier{locationOf(_e), value}; } } } @@ -61,6 +61,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e) for (auto const& var: m_value) { assertThrow(var.second, OptimizerException, ""); + assertThrow(inScope(var.first), OptimizerException, ""); if (SyntacticalEqualityChecker::equal(_e, *var.second)) { _e = Identifier{locationOf(_e), var.first}; diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index ca1e5153..7ac42c30 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -84,19 +84,19 @@ void DataFlowAnalyzer::operator()(Switch& _switch) void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) { - m_variableScopes.emplace_back(true); + pushScope(true); for (auto const& parameter: _fun.parameters) m_variableScopes.back().variables.insert(parameter.name); for (auto const& var: _fun.returnVariables) m_variableScopes.back().variables.insert(var.name); ASTModifier::operator()(_fun); - m_variableScopes.pop_back(); + popScope(); } void DataFlowAnalyzer::operator()(ForLoop& _for) { // Special scope handling of the pre block. - m_variableScopes.emplace_back(false); + pushScope(false); for (auto& statement: _for.pre.statements) visit(statement); @@ -110,16 +110,15 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) (*this)(_for.post); clearValues(assignments.names()); - - m_variableScopes.pop_back(); + popScope(); } void DataFlowAnalyzer::operator()(Block& _block) { size_t numScopes = m_variableScopes.size(); - m_variableScopes.emplace_back(false); + pushScope(false); ASTModifier::operator()(_block); - m_variableScopes.pop_back(); + popScope(); assertThrow(numScopes == m_variableScopes.size(), OptimizerException, ""); } @@ -148,7 +147,18 @@ void DataFlowAnalyzer::handleAssignment(set<string> const& _variables, Expressio } } -void DataFlowAnalyzer::clearValues(set<string> const& _variables) +void DataFlowAnalyzer::pushScope(bool _functionScope) +{ + m_variableScopes.emplace_back(_functionScope); +} + +void DataFlowAnalyzer::popScope() +{ + clearValues(std::move(m_variableScopes.back().variables)); + m_variableScopes.pop_back(); +} + +void DataFlowAnalyzer::clearValues(set<string> _variables) { // All variables that reference variables to be cleared also have to be // cleared, but not recursively, since only the value of the original @@ -163,16 +173,15 @@ void DataFlowAnalyzer::clearValues(set<string> const& _variables) // This cannot be easily tested since the substitutions will be done // one by one on the fly, and the last line will just be add(1, 1) - set<string> variables = _variables; // Clear variables that reference variables to be cleared. - for (auto const& name: variables) + for (auto const& name: _variables) for (auto const& ref: m_referencedBy[name]) - variables.insert(ref); + _variables.insert(ref); // Clear the value and update the reference relation. - for (auto const& name: variables) + for (auto const& name: _variables) m_value.erase(name); - for (auto const& name: variables) + for (auto const& name: _variables) { for (auto const& ref: m_references[name]) m_referencedBy[ref].erase(name); diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index f998eadf..95671467 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -56,9 +56,15 @@ protected: /// Registers the assignment. void handleAssignment(std::set<std::string> const& _names, Expression* _value); + /// Creates a new inner scope. + void pushScope(bool _functionScope); + + /// Removes the innermost scope and clears all variables in it. + void popScope(); + /// Clears information about the values assigned to the given variables, /// for example at points where control flow is merged. - void clearValues(std::set<std::string> const& _names); + void clearValues(std::set<std::string> _names); /// Returns true iff the variable is in scope. bool inScope(std::string const& _variableName) const; diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index ce71eda5..280d625a 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -40,12 +40,9 @@ using namespace dev; using namespace dev::yul; using namespace dev::solidity; -FullInliner::FullInliner(Block& _ast): - m_ast(_ast), m_nameDispenser(_ast) +FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): + m_ast(_ast), m_nameDispenser(_dispenser) { - assertThrow(m_ast.statements.size() >= 1, OptimizerException, ""); - assertThrow(m_ast.statements.front().type() == typeid(Block), OptimizerException, ""); - // Determine constants SSAValueTracker tracker; tracker(m_ast); @@ -54,10 +51,11 @@ FullInliner::FullInliner(Block& _ast): m_constants.insert(ssaValue.first); map<string, size_t> references = ReferencesCounter::countReferences(m_ast); - for (size_t i = 1; i < m_ast.statements.size(); ++i) + for (auto& statement: m_ast.statements) { - assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); - FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i)); + if (statement.type() != typeid(FunctionDefinition)) + continue; + FunctionDefinition& fun = boost::get<FunctionDefinition>(statement); m_functions[fun.name] = &fun; // Always inline functions that are only called once. if (references[fun.name] == 1) @@ -68,9 +66,10 @@ FullInliner::FullInliner(Block& _ast): void FullInliner::run() { - assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, ""); + for (auto& statement: m_ast.statements) + if (statement.type() == typeid(Block)) + handleBlock("", boost::get<Block>(statement)); - handleBlock("", boost::get<Block>(m_ast.statements[0])); // TODO it might be good to determine a visiting order: // first handle functions that are called from many places. for (auto const& fun: m_functions) diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index cd59ab46..5ecfb57a 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -71,7 +71,7 @@ class NameCollector; class FullInliner: public ASTModifier { public: - explicit FullInliner(Block& _ast); + explicit FullInliner(Block& _ast, NameDispenser& _dispenser); void run(); @@ -94,7 +94,7 @@ private: /// Variables that are constants (used for inlining heuristic) std::set<std::string> m_constants; std::map<std::string, size_t> m_functionSizes; - NameDispenser m_nameDispenser; + NameDispenser& m_nameDispenser; }; /** diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index dd6653ea..a99db0b6 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -38,16 +38,11 @@ void Rematerialiser::visit(Expression& _e) if (m_value.count(identifier.name)) { string name = identifier.name; - bool expressionValid = true; for (auto const& ref: m_references[name]) - if (!inScope(ref)) - { - expressionValid = false; - break; - } + assertThrow(inScope(ref), OptimizerException, ""); assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); - if (expressionValid && CodeSize::codeSize(value) <= 7) + if (CodeSize::codeSize(value) <= 7) _e = (ASTCopier{}).translate(value); } } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e0c6a2b6..844cef90 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -1118,7 +1118,15 @@ void CommandLineInterface::writeLinkedFiles() if (src.first == g_stdinFileName) cout << src.second << endl; else - writeFile(src.first, src.second); + { + ofstream outFile(src.first); + outFile << src.second; + if (!outFile) + { + cerr << "Could not write to file " << src.first << ". Aborting." << endl; + return; + } + } } string CommandLineInterface::libraryPlaceholderHint(string const& _libraryName) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7a496e64..133387c8 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -14010,6 +14010,21 @@ BOOST_AUTO_TEST_CASE(test_underscore_in_hex) ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(u256(0x1234abcd1234))); } +BOOST_AUTO_TEST_CASE(flipping_sign_tests) +{ + char const* sourceCode = R"( + contract test { + function f() public returns (bool){ + int x = -2**255; + assert(-x == x); + return true; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index f4ea6d67..6782f412 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -132,7 +132,7 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con (FunctionGrouper{})(*m_ast); NameDispenser nameDispenser(*m_ast); ExpressionSplitter{nameDispenser}(*m_ast); - FullInliner(*m_ast).run(); + FullInliner(*m_ast, nameDispenser).run(); ExpressionJoiner::run(*m_ast); } else if (m_optimizerStep == "mainFunction") diff --git a/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul b/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul new file mode 100644 index 00000000..2df4f9d0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockFlattener/switch_stmt.yul @@ -0,0 +1,22 @@ +{ + let a := 1 + switch calldataload(0) + case 0 { { { mstore(0, 1) } } a := 8 } + default { a := 3 { a := 4 } } + a := 5 +} +// ---- +// blockFlattener +// { +// let a := 1 +// switch calldataload(0) +// case 0 { +// mstore(0, 1) +// a := 8 +// } +// default { +// a := 3 +// a := 4 +// } +// a := 5 +// } diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul new file mode 100644 index 00000000..49b4c916 --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul @@ -0,0 +1,25 @@ +{ + let a := 10 + let x := 20 + { + let b := calldataload(0) + let d := calldataload(1) + x := d + } + // We had a bug where "calldataload(0)" was incorrectly replaced by "b" + mstore(0, calldataload(0)) + mstore(0, x) +} +// ---- +// commonSubexpressionEliminator +// { +// let a := 10 +// let x := 20 +// { +// let b := calldataload(0) +// let d := calldataload(1) +// x := d +// } +// mstore(0, calldataload(0)) +// mstore(0, x) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/operations.yul b/test/libyul/yulOptimizerTests/fullSimplify/operations.yul new file mode 100644 index 00000000..25467b62 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/operations.yul @@ -0,0 +1,44 @@ +{ + let x := mload(0) + mstore(1, mul(x, 0)) + mstore(2, div(x, 0)) + mstore(3, div(0, x)) + mstore(4, sdiv(x, 0)) + mstore(5, sdiv(0, x)) + mstore(6, and(0, x)) + mstore(7, and(x, 0)) + mstore(8, mod(0, x)) + mstore(9, mod(x, 0)) + mstore(10, lt(x, x)) + mstore(11, gt(x, x)) + mstore(12, slt(x, x)) + mstore(13, sgt(x, x)) + mstore(14, mod(x, x)) + mstore(15, and(x, not(x))) + mstore(16, and(not(x), x)) + mstore(17, or(x, not(x))) + mstore(18, or(not(x), x)) +} +// ---- +// fullSimplify +// { +// pop(mload(0)) +// mstore(1, 0) +// mstore(2, 0) +// mstore(3, 0) +// mstore(4, 0) +// mstore(5, 0) +// mstore(6, 0) +// mstore(7, 0) +// mstore(8, 0) +// mstore(9, 0) +// mstore(10, 0) +// mstore(11, 0) +// mstore(12, 0) +// mstore(13, 0) +// mstore(14, 0) +// mstore(15, 0) +// mstore(16, 0) +// mstore(17, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) +// mstore(18, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul b/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul new file mode 100644 index 00000000..714eb860 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/signextend.yul @@ -0,0 +1,12 @@ +{ + let x := 7 + mstore(0, signextend(50, x)) + let y := 255 + mstore(1, signextend(0, y)) +} +// ---- +// fullSimplify +// { +// mstore(0, 7) +// mstore(1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) +// } |