mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9823 from ethereum/develop
Merge develop into breaking.
This commit is contained in:
commit
9750286ee4
@ -9,6 +9,7 @@ Language Features:
|
||||
|
||||
Compiler Features:
|
||||
* Export compiler-generated utility sources via standard-json or combined-json.
|
||||
* SMTChecker: Support events and low-level logs.
|
||||
* SMTChecker: Support ``revert()``.
|
||||
* SMTChecker: Support shifts.
|
||||
* SMTChecker: Support structs.
|
||||
@ -16,13 +17,16 @@ Compiler Features:
|
||||
* Yul Optimizer: Prune unused parameters in functions.
|
||||
* Yul Optimizer: Inline into functions further down in the call graph first.
|
||||
* Yul Optimizer: Try to simplify function names.
|
||||
* Yul IR Generator: Report source locations related to unimplemented features.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``.
|
||||
* Type Checker: Disallow ``virtual`` for modifiers in libraries.
|
||||
* Type Checker: Correct the warning for homonymous, but not shadowing declarations.
|
||||
* ViewPureChecker: Prevent visibility check on constructors.
|
||||
* Type system: Fix internal error on implicit conversion of contract instance to the type of its ``super``.
|
||||
* Type system: Fix internal error on implicit conversion of string literal to a calldata string.
|
||||
* Type system: Fix named parameters in overloaded function and event calls being matched incorrectly if the order differs from the declaration.
|
||||
|
||||
### 0.7.1 (2020-09-02)
|
||||
|
@ -163,8 +163,13 @@ Homebrew formula directly from Github.
|
||||
View
|
||||
`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_.
|
||||
|
||||
Follow the history links until you have a raw file link of a
|
||||
specific commit of ``solidity.rb``.
|
||||
Copy the commit hash of the version you want and check it out on your machine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/ethereum/homebrew-ethereum.git
|
||||
cd homebrew-ethereum
|
||||
git checkout <your-hash-goes-here>
|
||||
|
||||
Install it using ``brew``:
|
||||
|
||||
@ -172,7 +177,7 @@ Install it using ``brew``:
|
||||
|
||||
brew unlink solidity
|
||||
# eg. Install 0.4.8
|
||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
||||
brew install solidity.rb
|
||||
|
||||
Gentoo Linux has an `Ethereum overlay <https://overlays.gentoo.org/#ethereum>`_ that contains a solidity package.
|
||||
After the overlay is setup, ``solc`` can be installed in x86_64 architectures by:
|
||||
|
@ -1110,6 +1110,7 @@ Abbreviation Full name
|
||||
``L`` ``LoadResolver``
|
||||
``M`` ``LoopInvariantCodeMotion``
|
||||
``r`` ``RedundantAssignEliminator``
|
||||
``R`` ``ReasoningBasedSimplifier`` - highly experimental
|
||||
``m`` ``Rematerialiser``
|
||||
``V`` ``SSAReverser``
|
||||
``a`` ``SSATransform``
|
||||
@ -1121,6 +1122,10 @@ Abbreviation Full name
|
||||
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
|
||||
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
|
||||
|
||||
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
|
||||
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
|
||||
and boolean conditions. It has not received thorough testing or validation yet and can produce
|
||||
non-reproducible results, so please use with care!
|
||||
|
||||
.. _erc20yul:
|
||||
|
||||
|
@ -9,6 +9,7 @@ set(sources
|
||||
SolverInterface.h
|
||||
Sorts.cpp
|
||||
Sorts.h
|
||||
Helpers.h
|
||||
)
|
||||
|
||||
if (${Z3_FOUND})
|
||||
|
58
libsmtutil/Helpers.h
Normal file
58
libsmtutil/Helpers.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsmtutil/SolverInterface.h>
|
||||
|
||||
namespace solidity::smtutil
|
||||
{
|
||||
|
||||
/// Signed division in SMTLIB2 rounds differently than EVM.
|
||||
/// This does not check for division by zero!
|
||||
inline Expression signedDivision(Expression _left, Expression _right)
|
||||
{
|
||||
return Expression::ite(
|
||||
_left >= 0,
|
||||
Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
|
||||
Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
|
||||
);
|
||||
}
|
||||
|
||||
inline Expression abs(Expression _value)
|
||||
{
|
||||
return Expression::ite(_value >= 0, _value, 0 - _value);
|
||||
}
|
||||
|
||||
/// Signed modulo in SMTLIB2 behaves differently with regards
|
||||
/// to the sign than EVM.
|
||||
/// This does not check for modulo by zero!
|
||||
inline Expression signedModulo(Expression _left, Expression _right)
|
||||
{
|
||||
return Expression::ite(
|
||||
_left >= 0,
|
||||
_left % _right,
|
||||
Expression::ite(
|
||||
(_left % _right) == 0,
|
||||
0,
|
||||
(_left % _right) - abs(_right)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -104,6 +104,8 @@ set(sources
|
||||
formal/ModelChecker.h
|
||||
formal/Predicate.cpp
|
||||
formal/Predicate.h
|
||||
formal/PredicateSort.cpp
|
||||
formal/PredicateSort.h
|
||||
formal/SMTEncoder.cpp
|
||||
formal/SMTEncoder.h
|
||||
formal/SSAVariable.cpp
|
||||
|
@ -1408,6 +1408,7 @@ BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo)
|
||||
return static_cast<size_t>(fixedBytes->numBytes()) >= m_value.size();
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_convertTo))
|
||||
return
|
||||
arrayType->location() != DataLocation::CallData &&
|
||||
arrayType->isByteArray() &&
|
||||
!(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer()) &&
|
||||
!(arrayType->isString() && !util::validateUTF8(value()));
|
||||
@ -3010,7 +3011,7 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const
|
||||
m_kind == Kind::BareStaticCall
|
||||
)
|
||||
for (auto& param: returnParameterTypes)
|
||||
if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage))
|
||||
if (param->isDynamicallyEncoded() && !param->dataStoredIn(DataLocation::Storage))
|
||||
param = TypeProvider::inaccessibleDynamic();
|
||||
|
||||
return returnParameterTypes;
|
||||
|
@ -164,7 +164,7 @@ string IRGenerator::generate(
|
||||
string IRGenerator::generate(Block const& _block)
|
||||
{
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
_block.accept(generator);
|
||||
generator.generate(_block);
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
@ -152,70 +154,130 @@ string IRGeneratorForStatements::code() const
|
||||
return m_code.str();
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::generate(Block const& _block)
|
||||
{
|
||||
try
|
||||
{
|
||||
_block.accept(*this);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
if (!_varDecl.value())
|
||||
return;
|
||||
try
|
||||
{
|
||||
setLocation(_varDecl);
|
||||
|
||||
_varDecl.value()->accept(*this);
|
||||
writeToLValue(
|
||||
_varDecl.immutable() ?
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
|
||||
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
|
||||
m_context.storageLocationOfStateVariable(_varDecl).second
|
||||
}},
|
||||
*_varDecl.value()
|
||||
);
|
||||
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
if (!_varDecl.value())
|
||||
return;
|
||||
|
||||
_varDecl.value()->accept(*this);
|
||||
writeToLValue(
|
||||
_varDecl.immutable() ?
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
|
||||
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
|
||||
m_context.storageLocationOfStateVariable(_varDecl).second
|
||||
}},
|
||||
*_varDecl.value()
|
||||
);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
|
||||
try
|
||||
{
|
||||
setLocation(_varDecl);
|
||||
|
||||
auto const* type = _varDecl.type();
|
||||
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
|
||||
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
|
||||
return;
|
||||
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
|
||||
|
||||
IRVariable zero = zeroValue(*type);
|
||||
assign(m_context.localVariable(_varDecl), zero);
|
||||
auto const* type = _varDecl.type();
|
||||
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
|
||||
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
|
||||
return;
|
||||
|
||||
IRVariable zero = zeroValue(*type);
|
||||
assign(m_context.localVariable(_varDecl), zero);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||
define(variable, _expression);
|
||||
return variable;
|
||||
try
|
||||
{
|
||||
setLocation(_expression);
|
||||
|
||||
_expression.accept(*this);
|
||||
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||
define(variable, _expression);
|
||||
return variable;
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant)
|
||||
{
|
||||
string functionName = IRNames::constantValueFunction(_constant);
|
||||
return m_context.functionCollector().createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> <ret> {
|
||||
<code>
|
||||
<ret> := <value>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
solAssert(_constant.value(), "");
|
||||
Type const& constantType = *_constant.type();
|
||||
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
|
||||
templ("code", generator.code());
|
||||
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
|
||||
try
|
||||
{
|
||||
setLocation(_constant);
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
string functionName = IRNames::constantValueFunction(_constant);
|
||||
return m_context.functionCollector().createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> <ret> {
|
||||
<code>
|
||||
<ret> := <value>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
solAssert(_constant.value(), "");
|
||||
Type const& constantType = *_constant.type();
|
||||
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
|
||||
templ("code", generator.code());
|
||||
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
||||
{
|
||||
setLocation(_varDeclStatement);
|
||||
|
||||
if (Expression const* expression = _varDeclStatement.initialValue())
|
||||
{
|
||||
if (_varDeclStatement.declarations().size() > 1)
|
||||
@ -249,14 +311,22 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional)
|
||||
{
|
||||
_conditional.condition().accept(*this);
|
||||
|
||||
setLocation(_conditional);
|
||||
|
||||
string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean());
|
||||
declare(_conditional);
|
||||
|
||||
m_code << "switch " << condition << "\n" "case 0 {\n";
|
||||
|
||||
_conditional.falseExpression().accept(*this);
|
||||
setLocation(_conditional);
|
||||
|
||||
assign(_conditional, _conditional.falseExpression());
|
||||
m_code << "}\n" "default {\n";
|
||||
|
||||
_conditional.trueExpression().accept(*this);
|
||||
setLocation(_conditional);
|
||||
|
||||
assign(_conditional, _conditional.trueExpression());
|
||||
m_code << "}\n";
|
||||
|
||||
@ -266,6 +336,7 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional)
|
||||
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
{
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
setLocation(_assignment);
|
||||
|
||||
Token assignmentOperator = _assignment.assignmentOperator();
|
||||
Token binaryOperator =
|
||||
@ -283,6 +354,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
|
||||
_assignment.leftHandSide().accept(*this);
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
setLocation(_assignment);
|
||||
|
||||
if (assignmentOperator != Token::Assign)
|
||||
{
|
||||
@ -323,6 +395,8 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
|
||||
bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
setLocation(_tuple);
|
||||
|
||||
if (_tuple.isInlineArray())
|
||||
{
|
||||
auto const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
||||
@ -339,6 +413,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
Expression const& component = *_tuple.components()[i];
|
||||
component.accept(*this);
|
||||
setLocation(_tuple);
|
||||
IRVariable converted = convert(component, baseType);
|
||||
m_code <<
|
||||
m_utils.writeToMemoryFunction(baseType) <<
|
||||
@ -358,6 +433,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
solAssert(_tuple.components().front(), "");
|
||||
_tuple.components().front()->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!!m_currentLValue, "");
|
||||
else
|
||||
@ -370,6 +446,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
if (auto const& component = _tuple.components()[i])
|
||||
{
|
||||
component->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue, "");
|
||||
@ -395,17 +472,20 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
|
||||
{
|
||||
_ifStatement.condition().accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean());
|
||||
|
||||
if (_ifStatement.falseStatement())
|
||||
{
|
||||
m_code << "switch " << condition << "\n" "case 0 {\n";
|
||||
_ifStatement.falseStatement()->accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
m_code << "}\n" "default {\n";
|
||||
}
|
||||
else
|
||||
m_code << "if " << condition << " {\n";
|
||||
_ifStatement.trueStatement().accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
m_code << "}\n";
|
||||
|
||||
return false;
|
||||
@ -413,6 +493,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
|
||||
|
||||
bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
|
||||
{
|
||||
setLocation(_forStatement);
|
||||
generateLoop(
|
||||
_forStatement.body(),
|
||||
_forStatement.condition(),
|
||||
@ -425,6 +506,7 @@ bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
|
||||
|
||||
bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
|
||||
{
|
||||
setLocation(_whileStatement);
|
||||
generateLoop(
|
||||
_whileStatement.body(),
|
||||
&_whileStatement.condition(),
|
||||
@ -436,20 +518,23 @@ bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Continue const&)
|
||||
bool IRGeneratorForStatements::visit(Continue const& _continue)
|
||||
{
|
||||
setLocation(_continue);
|
||||
m_code << "continue\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Break const&)
|
||||
bool IRGeneratorForStatements::visit(Break const& _break)
|
||||
{
|
||||
setLocation(_break);
|
||||
m_code << "break\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(Return const& _return)
|
||||
{
|
||||
setLocation(_return);
|
||||
if (Expression const* value = _return.expression())
|
||||
{
|
||||
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
|
||||
@ -466,6 +551,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
|
||||
{
|
||||
setLocation(_unaryOperation);
|
||||
Type const& resultType = type(_unaryOperation);
|
||||
Token const op = _unaryOperation.getOperator();
|
||||
|
||||
@ -551,6 +637,8 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
|
||||
|
||||
bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
{
|
||||
setLocation(_binOp);
|
||||
|
||||
solAssert(!!_binOp.annotation().commonType, "");
|
||||
TypePointer commonType = _binOp.annotation().commonType;
|
||||
langutil::Token op = _binOp.getOperator();
|
||||
@ -570,6 +658,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
|
||||
_binOp.leftExpression().accept(*this);
|
||||
_binOp.rightExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
|
||||
if (TokenTraits::isCompareOp(op))
|
||||
{
|
||||
@ -629,6 +718,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
|
||||
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
setLocation(_functionCall);
|
||||
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(&type(_functionCall.expression()));
|
||||
if (
|
||||
functionType &&
|
||||
@ -643,6 +733,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
setLocation(_functionCall);
|
||||
auto functionCallKind = *_functionCall.annotation().kind;
|
||||
|
||||
if (functionCallKind == FunctionCallKind::TypeConversion)
|
||||
@ -1360,6 +1451,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
|
||||
{
|
||||
setLocation(_options);
|
||||
FunctionType const& previousType = dynamic_cast<FunctionType const&>(*_options.expression().annotation().type);
|
||||
|
||||
solUnimplementedAssert(!previousType.bound(), "");
|
||||
@ -1379,6 +1471,7 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
setLocation(_memberAccess);
|
||||
ASTString const& member = _memberAccess.memberName();
|
||||
auto memberFunctionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
|
||||
Type::Category objectCategory = _memberAccess.expression().annotation().type->category();
|
||||
@ -1764,6 +1857,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
|
||||
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
{
|
||||
setLocation(_inlineAsm);
|
||||
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
|
||||
|
||||
yul::Statement modified = bodyCopier(_inlineAsm.operations());
|
||||
@ -1778,6 +1872,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
setLocation(_indexAccess);
|
||||
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
|
||||
|
||||
if (baseType.category() == Type::Category::Mapping)
|
||||
@ -1913,6 +2008,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess)
|
||||
{
|
||||
setLocation(_indexRangeAccess);
|
||||
Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type;
|
||||
solAssert(
|
||||
baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice,
|
||||
@ -1959,6 +2055,7 @@ void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAcces
|
||||
|
||||
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
setLocation(_identifier);
|
||||
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
||||
{
|
||||
@ -2017,6 +2114,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
|
||||
bool IRGeneratorForStatements::visit(Literal const& _literal)
|
||||
{
|
||||
setLocation(_literal);
|
||||
Type const& literalType = type(_literal);
|
||||
|
||||
switch (literalType.category())
|
||||
@ -2039,6 +2137,7 @@ void IRGeneratorForStatements::handleVariableReference(
|
||||
Expression const& _referencingExpression
|
||||
)
|
||||
{
|
||||
setLocation(_referencingExpression);
|
||||
if (_variable.isStateVariable() && _variable.isConstant())
|
||||
define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
|
||||
else if (_variable.isStateVariable() && _variable.immutable())
|
||||
@ -2480,6 +2579,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b
|
||||
solAssert(op == Token::Or || op == Token::And, "");
|
||||
|
||||
_binOp.leftExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
|
||||
IRVariable value(_binOp);
|
||||
define(value, _binOp.leftExpression());
|
||||
@ -2488,6 +2588,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b
|
||||
else
|
||||
m_code << "if " << value.name() << " {\n";
|
||||
_binOp.rightExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
assign(value, _binOp.rightExpression());
|
||||
m_code << "}\n";
|
||||
}
|
||||
@ -2687,6 +2788,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||
{
|
||||
Expression const& externalCall = _tryStatement.externalCall();
|
||||
externalCall.accept(*this);
|
||||
setLocation(_tryStatement);
|
||||
|
||||
m_code << "switch iszero(" << IRNames::trySuccessConditionVariable(externalCall) << ")\n";
|
||||
|
||||
@ -2707,6 +2809,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||
}
|
||||
|
||||
successClause.block().accept(*this);
|
||||
setLocation(_tryStatement);
|
||||
m_code << "}\n";
|
||||
|
||||
m_code << "default { // failure case\n";
|
||||
@ -2797,3 +2900,8 @@ bool IRGeneratorForStatements::visit(TryCatchClause const& _clause)
|
||||
_clause.block().accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::setLocation(ASTNode const& _node)
|
||||
{
|
||||
m_currentLocation = _node.location();
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ public:
|
||||
|
||||
std::string code() const;
|
||||
|
||||
/// Generate the code for the statements in the block;
|
||||
void generate(Block const& _block);
|
||||
|
||||
/// Generates code to initialize the given state variable.
|
||||
void initializeStateVar(VariableDeclaration const& _varDecl);
|
||||
/// Generates code to initialize the given local variable.
|
||||
@ -179,10 +182,13 @@ private:
|
||||
|
||||
static Type const& type(Expression const& _expression);
|
||||
|
||||
void setLocation(ASTNode const& _node);
|
||||
|
||||
std::ostringstream m_code;
|
||||
IRGenerationContext& m_context;
|
||||
YulUtilFunctions& m_utils;
|
||||
std::optional<IRLValue> m_currentLValue;
|
||||
langutil::SourceLocation m_currentLocation;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <libsmtutil/Z3CHCInterface.h>
|
||||
#endif
|
||||
|
||||
#include <libsolidity/formal/PredicateSort.h>
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
@ -39,13 +40,14 @@ using namespace solidity::util;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::smtutil;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::frontend::smt;
|
||||
|
||||
CHC::CHC(
|
||||
smt::EncodingContext& _context,
|
||||
EncodingContext& _context,
|
||||
ErrorReporter& _errorReporter,
|
||||
[[maybe_unused]] map<util::h256, string> const& _smtlib2Responses,
|
||||
[[maybe_unused]] ReadCallback::Callback const& _smtCallback,
|
||||
smtutil::SMTSolverChoice _enabledSolvers
|
||||
SMTSolverChoice _enabledSolvers
|
||||
):
|
||||
SMTEncoder(_context),
|
||||
m_outerErrorReporter(_errorReporter),
|
||||
@ -56,7 +58,7 @@ CHC::CHC(
|
||||
usesZ3 = false;
|
||||
#endif
|
||||
if (!usesZ3)
|
||||
m_interface = make_unique<smtutil::CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback);
|
||||
m_interface = make_unique<CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback);
|
||||
}
|
||||
|
||||
void CHC::analyze(SourceUnit const& _source)
|
||||
@ -79,7 +81,7 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
|
||||
vector<string> CHC::unhandledQueries() const
|
||||
{
|
||||
if (auto smtlib2 = dynamic_cast<smtutil::CHCSmtLib2Interface const*>(m_interface.get()))
|
||||
if (auto smtlib2 = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get()))
|
||||
return smtlib2->unhandledQueries();
|
||||
|
||||
return {};
|
||||
@ -92,13 +94,15 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
initContract(_contract);
|
||||
|
||||
m_stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
m_stateSorts = stateSorts(_contract);
|
||||
|
||||
clearIndices(&_contract);
|
||||
|
||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix, &_contract);
|
||||
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix, &_contract);
|
||||
solAssert(m_currentContract, "");
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(
|
||||
constructorSort(*m_currentContract),
|
||||
"summary_constructor_" + contractSuffix(_contract),
|
||||
&_contract
|
||||
);
|
||||
auto stateExprs = currentStateVariables();
|
||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||
|
||||
@ -108,7 +112,12 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
auto implicitConstructor = (*m_implicitConstructorPredicate)({});
|
||||
auto implicitConstructorPredicate = createSymbolicBlock(
|
||||
implicitConstructorSort(),
|
||||
"implicit_constructor_" + contractSuffix(_contract),
|
||||
&_contract
|
||||
);
|
||||
auto implicitConstructor = (*implicitConstructorPredicate)({});
|
||||
addRule(implicitConstructor, implicitConstructor.name);
|
||||
m_currentBlock = implicitConstructor;
|
||||
m_context.addAssertion(m_error.currentValue() == 0);
|
||||
@ -204,7 +213,12 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
||||
if (_function.isConstructor())
|
||||
{
|
||||
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix, m_currentContract);
|
||||
solAssert(m_currentContract, "");
|
||||
auto constructorExit = createSymbolicBlock(
|
||||
constructorSort(*m_currentContract),
|
||||
"constructor_exit_" + suffix,
|
||||
m_currentContract
|
||||
);
|
||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentFunctionVariables(*m_currentContract)));
|
||||
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
@ -566,7 +580,7 @@ void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop)
|
||||
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_arrayPop.expression());
|
||||
solAssert(memberAccess, "");
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
|
||||
auto symbArray = dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
|
||||
solAssert(symbArray, "");
|
||||
|
||||
auto previousError = m_error.currentValue();
|
||||
@ -662,15 +676,15 @@ void CHC::resetSourceAnalysis()
|
||||
if (usesZ3)
|
||||
{
|
||||
/// z3::fixedpoint does not have a reset mechanism, so we need to create another.
|
||||
m_interface.reset(new smtutil::Z3CHCInterface());
|
||||
auto z3Interface = dynamic_cast<smtutil::Z3CHCInterface const*>(m_interface.get());
|
||||
m_interface.reset(new Z3CHCInterface());
|
||||
auto z3Interface = dynamic_cast<Z3CHCInterface const*>(m_interface.get());
|
||||
solAssert(z3Interface, "");
|
||||
m_context.setSolver(z3Interface->z3Interface());
|
||||
}
|
||||
#endif
|
||||
if (!usesZ3)
|
||||
{
|
||||
auto smtlib2Interface = dynamic_cast<smtutil::CHCSmtLib2Interface*>(m_interface.get());
|
||||
auto smtlib2Interface = dynamic_cast<CHCSmtLib2Interface*>(m_interface.get());
|
||||
smtlib2Interface->reset();
|
||||
solAssert(smtlib2Interface, "");
|
||||
m_context.setSolver(smtlib2Interface->smtlib2Interface());
|
||||
@ -682,7 +696,6 @@ void CHC::resetSourceAnalysis()
|
||||
|
||||
void CHC::resetContractAnalysis()
|
||||
{
|
||||
m_stateSorts.clear();
|
||||
m_stateVariables.clear();
|
||||
m_unknownFunctionCallSeen = false;
|
||||
m_breakDest = nullptr;
|
||||
@ -739,117 +752,18 @@ set<frontend::Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTN
|
||||
return assertions;
|
||||
}
|
||||
|
||||
vector<smtutil::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||
SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
return applyMap(
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
return functionSort(_function, m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::constructorSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
if (auto const* constructor = m_currentContract->constructor())
|
||||
return sort(*constructor);
|
||||
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + m_stateSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::interfaceSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return interfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::nondetInterfaceSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return nondetInterfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
stateSorts(_contract),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::nondetInterfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
auto sorts = stateSorts(_contract);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
sorts + sorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::arity0FunctionSort() const
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>(),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
/// A function in the symbolic CFG requires:
|
||||
/// - Index of failed assertion. 0 means no assertion failed.
|
||||
/// - 2 sets of state variables:
|
||||
/// - State variables at the beginning of the current function, immutable
|
||||
/// - Current state variables
|
||||
/// At the beginning of the function these must equal set 1
|
||||
/// - 2 sets of input variables:
|
||||
/// - Input variables at the beginning of the current function, immutable
|
||||
/// - Current input variables
|
||||
/// At the beginning of the function these must equal set 1
|
||||
/// - 1 set of output variables
|
||||
smtutil::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::sort(ASTNode const* _node)
|
||||
SortPointer CHC::sort(ASTNode const* _node)
|
||||
{
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||
return sort(*funDef);
|
||||
|
||||
auto fSort = dynamic_pointer_cast<smtutil::FunctionSort>(sort(*m_currentFunction));
|
||||
solAssert(fSort, "");
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
fSort->domain + applyMap(m_currentFunction->localVariables(), smtSort),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
auto sorts = stateSorts(_contract);
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} +
|
||||
sorts +
|
||||
inputSorts +
|
||||
sorts +
|
||||
inputSorts +
|
||||
outputSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
solAssert(m_currentFunction, "");
|
||||
return functionBodySort(*m_currentFunction, m_currentContract);
|
||||
}
|
||||
|
||||
Predicate const* CHC::createSymbolicBlock(SortPointer _sort, string const& _name, ASTNode const* _node)
|
||||
@ -987,7 +901,7 @@ Predicate const* CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||
Predicate const* CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto block = createSymbolicBlock(
|
||||
summarySort(_function, _contract),
|
||||
functionSort(_function, &_contract),
|
||||
"summary_" + uniquePrefix() + "_" + predicateName(&_function, &_contract),
|
||||
&_function
|
||||
);
|
||||
@ -1157,14 +1071,14 @@ void CHC::addRule(smtutil::Expression const& _rule, string const& _ruleName)
|
||||
m_interface->addRule(_rule, _ruleName);
|
||||
}
|
||||
|
||||
pair<smtutil::CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
pair<CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
{
|
||||
smtutil::CheckResult result;
|
||||
CheckResult result;
|
||||
CHCSolverInterface::CexGraph cex;
|
||||
tie(result, cex) = m_interface->query(_query);
|
||||
switch (result)
|
||||
{
|
||||
case smtutil::CheckResult::SATISFIABLE:
|
||||
case CheckResult::SATISFIABLE:
|
||||
{
|
||||
#ifdef HAVE_Z3
|
||||
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
|
||||
@ -1173,25 +1087,25 @@ pair<smtutil::CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Exp
|
||||
solAssert(spacer, "");
|
||||
spacer->setSpacerOptions(false);
|
||||
|
||||
smtutil::CheckResult resultNoOpt;
|
||||
CheckResult resultNoOpt;
|
||||
CHCSolverInterface::CexGraph cexNoOpt;
|
||||
tie(resultNoOpt, cexNoOpt) = m_interface->query(_query);
|
||||
|
||||
if (resultNoOpt == smtutil::CheckResult::SATISFIABLE)
|
||||
if (resultNoOpt == CheckResult::SATISFIABLE)
|
||||
cex = move(cexNoOpt);
|
||||
|
||||
spacer->setSpacerOptions(true);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case smtutil::CheckResult::UNSATISFIABLE:
|
||||
case CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smtutil::CheckResult::UNKNOWN:
|
||||
case CheckResult::UNKNOWN:
|
||||
break;
|
||||
case smtutil::CheckResult::CONFLICTING:
|
||||
case CheckResult::CONFLICTING:
|
||||
m_outerErrorReporter.warning(1988_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
break;
|
||||
case smtutil::CheckResult::ERROR:
|
||||
case CheckResult::ERROR:
|
||||
m_outerErrorReporter.warning(1218_error, _location, "Error trying to invoke SMT solver.");
|
||||
break;
|
||||
}
|
||||
@ -1339,9 +1253,9 @@ void CHC::checkAndReportTarget(
|
||||
createErrorBlock();
|
||||
connectBlocks(_target.value, error(), _target.constraints && (_target.errorId == _errorId));
|
||||
auto const& [result, model] = query(error(), _scope->location());
|
||||
if (result == smtutil::CheckResult::UNSATISFIABLE)
|
||||
if (result == CheckResult::UNSATISFIABLE)
|
||||
m_safeTargets[_scope].insert(_target.type);
|
||||
else if (result == smtutil::CheckResult::SATISFIABLE)
|
||||
else if (result == CheckResult::SATISFIABLE)
|
||||
{
|
||||
solAssert(!_satMsg.empty(), "");
|
||||
m_unsafeTargets[_scope].insert(_target.type);
|
||||
@ -1477,7 +1391,7 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
return localState + "\nTransaction trace:\n" + boost::algorithm::join(boost::adaptors::reverse(path), "\n");
|
||||
}
|
||||
|
||||
string CHC::cex2dot(smtutil::CHCSolverInterface::CexGraph const& _cex)
|
||||
string CHC::cex2dot(CHCSolverInterface::CexGraph const& _cex)
|
||||
{
|
||||
string dot = "digraph {\n";
|
||||
|
||||
@ -1498,6 +1412,11 @@ string CHC::uniquePrefix()
|
||||
return to_string(m_blockCounter++);
|
||||
}
|
||||
|
||||
string CHC::contractSuffix(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id());
|
||||
}
|
||||
|
||||
unsigned CHC::newErrorId(frontend::Expression const& _expr)
|
||||
{
|
||||
unsigned errorId = m_context.newUniqueId();
|
||||
|
@ -117,19 +117,8 @@ private:
|
||||
|
||||
/// Sort helpers.
|
||||
//@{
|
||||
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||
smtutil::SortPointer constructorSort();
|
||||
smtutil::SortPointer interfaceSort();
|
||||
smtutil::SortPointer nondetInterfaceSort();
|
||||
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
|
||||
smtutil::SortPointer arity0FunctionSort() const;
|
||||
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
||||
smtutil::SortPointer sort(ASTNode const* _block);
|
||||
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
|
||||
/// where the sort changes because the set of state variables might change.
|
||||
static smtutil::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||
//@}
|
||||
|
||||
/// Predicate helpers.
|
||||
@ -246,10 +235,13 @@ private:
|
||||
|
||||
/// Misc.
|
||||
//@{
|
||||
/// Returns a prefix to be used in a new unique block name
|
||||
/// @returns a prefix to be used in a new unique block name
|
||||
/// and increases the block counter.
|
||||
std::string uniquePrefix();
|
||||
|
||||
/// @returns a suffix to be used by contract related predicates.
|
||||
std::string contractSuffix(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns a new unique error id associated with _expr and stores
|
||||
/// it into m_errorIds.
|
||||
unsigned newErrorId(Expression const& _expr);
|
||||
@ -257,10 +249,6 @@ private:
|
||||
|
||||
/// Predicates.
|
||||
//@{
|
||||
/// Implicit constructor predicate.
|
||||
/// Explicit constructors are handled as functions.
|
||||
Predicate const* m_implicitConstructorPredicate = nullptr;
|
||||
|
||||
/// Constructor summary predicate, exists after the constructor
|
||||
/// (implicit or explicit) and before the interface.
|
||||
Predicate const* m_constructorSummaryPredicate = nullptr;
|
||||
@ -292,9 +280,6 @@ private:
|
||||
|
||||
/// Variables.
|
||||
//@{
|
||||
/// State variables sorts.
|
||||
/// Used by all predicates.
|
||||
std::vector<smtutil::SortPointer> m_stateSorts;
|
||||
/// State variables.
|
||||
/// Used to create all predicates.
|
||||
std::vector<VariableDeclaration const*> m_stateVariables;
|
||||
|
111
libsolidity/formal/PredicateSort.cpp
Normal file
111
libsolidity/formal/PredicateSort.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/formal/PredicateSort.h>
|
||||
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
{
|
||||
|
||||
SortPointer interfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
return make_shared<FunctionSort>(
|
||||
stateSorts(_contract),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer nondetInterfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
auto varSorts = stateSorts(_contract);
|
||||
return make_shared<FunctionSort>(
|
||||
varSorts + varSorts,
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer implicitConstructorSort()
|
||||
{
|
||||
return arity0FunctionSort();
|
||||
}
|
||||
|
||||
SortPointer constructorSort(ContractDefinition const& _contract)
|
||||
{
|
||||
if (auto const* constructor = _contract.constructor())
|
||||
return functionSort(*constructor, &_contract);
|
||||
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>{SortProvider::uintSort} + stateSorts(_contract),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer functionSort(FunctionDefinition const& _function, ContractDefinition const* _contract)
|
||||
{
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto varSorts = _contract ? stateSorts(*_contract) : vector<SortPointer>{};
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>{SortProvider::uintSort} +
|
||||
varSorts +
|
||||
inputSorts +
|
||||
varSorts +
|
||||
inputSorts +
|
||||
outputSorts,
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer functionBodySort(FunctionDefinition const& _function, ContractDefinition const* _contract)
|
||||
{
|
||||
auto fSort = dynamic_pointer_cast<FunctionSort>(functionSort(_function, _contract));
|
||||
solAssert(fSort, "");
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
return make_shared<FunctionSort>(
|
||||
fSort->domain + applyMap(_function.localVariables(), smtSort),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer arity0FunctionSort()
|
||||
{
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>(),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
/// Helpers
|
||||
|
||||
vector<SortPointer> stateSorts(ContractDefinition const& _contract)
|
||||
{
|
||||
return applyMap(
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
82
libsolidity/formal/PredicateSort.h
Normal file
82
libsolidity/formal/PredicateSort.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/Predicate.h>
|
||||
|
||||
#include <libsmtutil/Sorts.h>
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
{
|
||||
|
||||
/**
|
||||
* This file represents the specification for CHC predicate sorts.
|
||||
* Types of predicates:
|
||||
*
|
||||
* 1. Interface
|
||||
* The idle state of a contract. Signature:
|
||||
* interface(stateVariables).
|
||||
*
|
||||
* 2. Nondet interface
|
||||
* The nondeterminism behavior of a contract. Signature:
|
||||
* nondet_interface(stateVariables, stateVariables').
|
||||
*
|
||||
* 3. Implicit constructor
|
||||
* The implicit constructor of a contract, that is, without input parameters. Signature:
|
||||
* implicit_constructor().
|
||||
*
|
||||
* 4. Constructor entry/summary
|
||||
* The summary of an implicit constructor. Signature:
|
||||
* constructor_summary(error, stateVariables').
|
||||
*
|
||||
* 5. Function entry/summary
|
||||
* The entry point of a function definition. Signature:
|
||||
* function_entry(error, stateVariables, inputVariables, stateVariables', inputVariables', outputVariables').
|
||||
*
|
||||
* 6. Function body
|
||||
* Use for any predicate within a function. Signature:
|
||||
* function_body(error, stateVariables, inputVariables, stateVariables', inputVariables', outputVariables', localVariables).
|
||||
*/
|
||||
|
||||
/// @returns the interface predicate sort for _contract.
|
||||
smtutil::SortPointer interfaceSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the nondeterminisc interface predicate sort for _contract.
|
||||
smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the implicit constructor predicate sort.
|
||||
smtutil::SortPointer implicitConstructorSort();
|
||||
|
||||
/// @returns the constructor entry/summary predicate sort for _contract.
|
||||
smtutil::SortPointer constructorSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the function entry/summary predicate sort for _function contained in _contract.
|
||||
smtutil::SortPointer functionSort(FunctionDefinition const& _function, ContractDefinition const* _contract);
|
||||
|
||||
/// @returns the function body predicate sort for _function contained in _contract.
|
||||
smtutil::SortPointer functionBodySort(FunctionDefinition const& _function, ContractDefinition const* _contract);
|
||||
|
||||
/// @returns the sort of a predicate without parameters.
|
||||
smtutil::SortPointer arity0FunctionSort();
|
||||
|
||||
/// Helpers
|
||||
|
||||
std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract) ;
|
||||
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <libsmtutil/SMTPortfolio.h>
|
||||
#include <libsmtutil/Helpers.h>
|
||||
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
@ -675,6 +676,14 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
case FunctionType::Kind::ArrayPop:
|
||||
arrayPop(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Log0:
|
||||
case FunctionType::Kind::Log1:
|
||||
case FunctionType::Kind::Log2:
|
||||
case FunctionType::Kind::Log3:
|
||||
case FunctionType::Kind::Log4:
|
||||
case FunctionType::Kind::Event:
|
||||
// These can be safely ignored.
|
||||
break;
|
||||
default:
|
||||
m_errorReporter.warning(
|
||||
4588_error,
|
||||
@ -1497,11 +1506,7 @@ smtutil::Expression SMTEncoder::division(smtutil::Expression _left, smtutil::Exp
|
||||
{
|
||||
// Signed division in SMTLIB2 rounds differently for negative division.
|
||||
if (_type.isSigned())
|
||||
return (smtutil::Expression::ite(
|
||||
_left >= 0,
|
||||
smtutil::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
|
||||
smtutil::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
|
||||
));
|
||||
return signedDivision(_left, _right);
|
||||
else
|
||||
return _left / _right;
|
||||
}
|
||||
|
@ -524,6 +524,10 @@ bool CompilerStack::compile()
|
||||
{
|
||||
if (m_generateEvmBytecode)
|
||||
compileContract(*contract, otherCompilers);
|
||||
if (m_generateIR || m_generateEwasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEwasm)
|
||||
generateEwasm(*contract);
|
||||
}
|
||||
catch (Error const& _error)
|
||||
{
|
||||
@ -532,10 +536,28 @@ bool CompilerStack::compile()
|
||||
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
|
||||
return false;
|
||||
}
|
||||
if (m_generateIR || m_generateEwasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEwasm)
|
||||
generateEwasm(*contract);
|
||||
catch (UnimplementedFeatureError const& _unimplementedError)
|
||||
{
|
||||
if (
|
||||
SourceLocation const* sourceLocation =
|
||||
boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError)
|
||||
)
|
||||
{
|
||||
string const* comment = _unimplementedError.comment();
|
||||
m_errorReporter.error(
|
||||
1834_error,
|
||||
Error::Type::CodeGenerationError,
|
||||
*sourceLocation,
|
||||
"Unimplemented feature error" +
|
||||
((comment && !comment->empty()) ? ": " + *comment : string{}) +
|
||||
" in " +
|
||||
_unimplementedError.lineInfo()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
m_stackState = CompilationSuccessful;
|
||||
this->link();
|
||||
|
@ -132,6 +132,8 @@ add_library(yul
|
||||
optimiser/OptimiserStep.h
|
||||
optimiser/OptimizerUtilities.cpp
|
||||
optimiser/OptimizerUtilities.h
|
||||
optimiser/ReasoningBasedSimplifier.cpp
|
||||
optimiser/ReasoningBasedSimplifier.h
|
||||
optimiser/RedundantAssignEliminator.cpp
|
||||
optimiser/RedundantAssignEliminator.h
|
||||
optimiser/Rematerialiser.cpp
|
||||
@ -168,4 +170,5 @@ add_library(yul
|
||||
optimiser/VarNameCleaner.cpp
|
||||
optimiser/VarNameCleaner.h
|
||||
)
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil)
|
||||
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil)
|
@ -33,6 +33,15 @@ Literal Dialect::zeroLiteralForType(solidity::yul::YulString _type) const
|
||||
return {SourceLocation{}, LiteralKind::Number, "0"_yulstring, _type};
|
||||
}
|
||||
|
||||
|
||||
Literal Dialect::trueLiteral() const
|
||||
{
|
||||
if (boolType != defaultType)
|
||||
return {SourceLocation{}, LiteralKind::Boolean, "true"_yulstring, boolType};
|
||||
else
|
||||
return {SourceLocation{}, LiteralKind::Number, "1"_yulstring, defaultType};
|
||||
}
|
||||
|
||||
bool Dialect::validTypeForLiteral(LiteralKind _kind, YulString, YulString _type) const
|
||||
{
|
||||
if (_kind == LiteralKind::Boolean)
|
||||
|
@ -77,6 +77,7 @@ struct Dialect: boost::noncopyable
|
||||
virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const;
|
||||
|
||||
virtual Literal zeroLiteralForType(YulString _type) const;
|
||||
virtual Literal trueLiteral() const;
|
||||
|
||||
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
@ -49,17 +50,41 @@ struct OptimiserStep
|
||||
virtual ~OptimiserStep() = default;
|
||||
|
||||
virtual void run(OptimiserStepContext&, Block&) const = 0;
|
||||
/// @returns non-nullopt if the step cannot be run, for example because it requires
|
||||
/// an SMT solver to be loaded, but none is available. In that case, the string
|
||||
/// contains a human-readable reason.
|
||||
virtual std::optional<std::string> invalidInCurrentEnvironment() const = 0;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
template <class Step>
|
||||
struct OptimiserStepInstance: public OptimiserStep
|
||||
{
|
||||
private:
|
||||
template<typename T>
|
||||
struct HasInvalidInCurrentEnvironmentMethod
|
||||
{
|
||||
private:
|
||||
template<typename U> static auto test(int) -> decltype(U::invalidInCurrentEnvironment(), std::true_type());
|
||||
template<typename> static std::false_type test(...);
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(test<T>(0))::value;
|
||||
};
|
||||
|
||||
public:
|
||||
OptimiserStepInstance(): OptimiserStep{Step::name} {}
|
||||
void run(OptimiserStepContext& _context, Block& _ast) const override
|
||||
{
|
||||
Step::run(_context, _ast);
|
||||
}
|
||||
std::optional<std::string> invalidInCurrentEnvironment() const override
|
||||
{
|
||||
if constexpr (HasInvalidInCurrentEnvironmentMethod<Step>::value)
|
||||
return Step::invalidInCurrentEnvironment();
|
||||
else
|
||||
return std::nullopt;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
339
libyul/optimiser/ReasoningBasedSimplifier.cpp
Normal file
339
libyul/optimiser/ReasoningBasedSimplifier.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
/*
|
||||
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 <libyul/optimiser/ReasoningBasedSimplifier.h>
|
||||
|
||||
#include <libyul/optimiser/SSAValueTracker.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/Dialect.h>
|
||||
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <libsmtutil/SMTPortfolio.h>
|
||||
#include <libsmtutil/Helpers.h>
|
||||
|
||||
#include <libsolutil/Visitor.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::yul;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
void ReasoningBasedSimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
set<YulString> ssaVars = SSAValueTracker::ssaVariables(_ast);
|
||||
ReasoningBasedSimplifier{_context.dialect, ssaVars}(_ast);
|
||||
}
|
||||
|
||||
std::optional<string> ReasoningBasedSimplifier::invalidInCurrentEnvironment()
|
||||
{
|
||||
// SMTLib2 interface is always available, but we would like to have synchronous answers.
|
||||
if (smtutil::SMTPortfolio{}.solvers() <= 1)
|
||||
return string{"No SMT solvers available."};
|
||||
else
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
void ReasoningBasedSimplifier::operator()(VariableDeclaration& _varDecl)
|
||||
{
|
||||
if (_varDecl.variables.size() != 1 || !_varDecl.value)
|
||||
return;
|
||||
YulString varName = _varDecl.variables.front().name;
|
||||
if (!m_ssaVariables.count(varName))
|
||||
return;
|
||||
bool const inserted = m_variables.insert({varName, m_solver->newVariable("yul_" + varName.str(), defaultSort())}).second;
|
||||
yulAssert(inserted, "");
|
||||
m_solver->addAssertion(m_variables.at(varName) == encodeExpression(*_varDecl.value));
|
||||
}
|
||||
|
||||
void ReasoningBasedSimplifier::operator()(If& _if)
|
||||
{
|
||||
if (!SideEffectsCollector{m_dialect, *_if.condition}.movable())
|
||||
return;
|
||||
|
||||
smtutil::Expression condition = encodeExpression(*_if.condition);
|
||||
m_solver->push();
|
||||
m_solver->addAssertion(condition == constantValue(0));
|
||||
CheckResult result = m_solver->check({}).first;
|
||||
m_solver->pop();
|
||||
if (result == CheckResult::UNSATISFIABLE)
|
||||
{
|
||||
Literal trueCondition = m_dialect.trueLiteral();
|
||||
trueCondition.location = locationOf(*_if.condition);
|
||||
_if.condition = make_unique<yul::Expression>(move(trueCondition));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_solver->push();
|
||||
m_solver->addAssertion(condition != constantValue(0));
|
||||
CheckResult result2 = m_solver->check({}).first;
|
||||
m_solver->pop();
|
||||
if (result2 == CheckResult::UNSATISFIABLE)
|
||||
{
|
||||
Literal falseCondition = m_dialect.zeroLiteralForType(m_dialect.boolType);
|
||||
falseCondition.location = locationOf(*_if.condition);
|
||||
_if.condition = make_unique<yul::Expression>(move(falseCondition));
|
||||
_if.body = yul::Block{};
|
||||
// Nothing left to be done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_solver->push();
|
||||
m_solver->addAssertion(condition != constantValue(0));
|
||||
|
||||
ASTModifier::operator()(_if.body);
|
||||
|
||||
m_solver->pop();
|
||||
}
|
||||
|
||||
ReasoningBasedSimplifier::ReasoningBasedSimplifier(
|
||||
Dialect const& _dialect,
|
||||
set<YulString> const& _ssaVariables
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_ssaVariables(_ssaVariables),
|
||||
m_solver(make_unique<smtutil::SMTPortfolio>())
|
||||
{
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::encodeExpression(yul::Expression const& _expression)
|
||||
{
|
||||
return std::visit(GenericVisitor{
|
||||
[&](FunctionCall const& _functionCall)
|
||||
{
|
||||
if (auto const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||
if (auto const* builtin = dialect->builtin(_functionCall.functionName.name))
|
||||
if (builtin->instruction)
|
||||
return encodeEVMBuiltin(*builtin->instruction, _functionCall.arguments);
|
||||
return newRestrictedVariable();
|
||||
},
|
||||
[&](Identifier const& _identifier)
|
||||
{
|
||||
if (
|
||||
m_ssaVariables.count(_identifier.name) &&
|
||||
m_variables.count(_identifier.name)
|
||||
)
|
||||
return m_variables.at(_identifier.name);
|
||||
else
|
||||
return newRestrictedVariable();
|
||||
},
|
||||
[&](Literal const& _literal)
|
||||
{
|
||||
return literalValue(_literal);
|
||||
}
|
||||
}, _expression);
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::encodeEVMBuiltin(
|
||||
evmasm::Instruction _instruction,
|
||||
vector<yul::Expression> const& _arguments
|
||||
)
|
||||
{
|
||||
vector<smtutil::Expression> arguments = applyMap(
|
||||
_arguments,
|
||||
[this](yul::Expression const& _expr) { return encodeExpression(_expr); }
|
||||
);
|
||||
switch (_instruction)
|
||||
{
|
||||
case evmasm::Instruction::ADD:
|
||||
return wrap(arguments.at(0) + arguments.at(1));
|
||||
case evmasm::Instruction::MUL:
|
||||
return wrap(arguments.at(0) * arguments.at(1));
|
||||
case evmasm::Instruction::SUB:
|
||||
return wrap(arguments.at(0) - arguments.at(1));
|
||||
case evmasm::Instruction::DIV:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(1) == constantValue(0),
|
||||
constantValue(0),
|
||||
arguments.at(0) / arguments.at(1)
|
||||
);
|
||||
case evmasm::Instruction::SDIV:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(1) == constantValue(0),
|
||||
constantValue(0),
|
||||
// No `wrap()` needed here, because -2**255 / -1 results
|
||||
// in 2**255 which is "converted" to its two's complement
|
||||
// representation 2**255 in `signedToUnsigned`
|
||||
signedToUnsigned(smtutil::signedDivision(
|
||||
unsignedToSigned(arguments.at(0)),
|
||||
unsignedToSigned(arguments.at(1))
|
||||
))
|
||||
);
|
||||
break;
|
||||
case evmasm::Instruction::MOD:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(1) == constantValue(0),
|
||||
constantValue(0),
|
||||
arguments.at(0) % arguments.at(1)
|
||||
);
|
||||
case evmasm::Instruction::SMOD:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(1) == constantValue(0),
|
||||
constantValue(0),
|
||||
signedToUnsigned(signedModulo(
|
||||
unsignedToSigned(arguments.at(0)),
|
||||
unsignedToSigned(arguments.at(1))
|
||||
))
|
||||
);
|
||||
break;
|
||||
case evmasm::Instruction::LT:
|
||||
return booleanValue(arguments.at(0) < arguments.at(1));
|
||||
case evmasm::Instruction::SLT:
|
||||
return booleanValue(unsignedToSigned(arguments.at(0)) < unsignedToSigned(arguments.at(1)));
|
||||
case evmasm::Instruction::GT:
|
||||
return booleanValue(arguments.at(0) > arguments.at(1));
|
||||
case evmasm::Instruction::SGT:
|
||||
return booleanValue(unsignedToSigned(arguments.at(0)) > unsignedToSigned(arguments.at(1)));
|
||||
case evmasm::Instruction::EQ:
|
||||
return booleanValue(arguments.at(0) == arguments.at(1));
|
||||
case evmasm::Instruction::ISZERO:
|
||||
return booleanValue(arguments.at(0) == constantValue(0));
|
||||
case evmasm::Instruction::AND:
|
||||
return smtutil::Expression::ite(
|
||||
(arguments.at(0) == 0 || arguments.at(0) == 1) &&
|
||||
(arguments.at(1) == 0 || arguments.at(1) == 1),
|
||||
booleanValue(arguments.at(0) == 1 && arguments.at(1) == 1),
|
||||
bv2int(int2bv(arguments.at(0)) & int2bv(arguments.at(1)))
|
||||
);
|
||||
case evmasm::Instruction::OR:
|
||||
return smtutil::Expression::ite(
|
||||
(arguments.at(0) == 0 || arguments.at(0) == 1) &&
|
||||
(arguments.at(1) == 0 || arguments.at(1) == 1),
|
||||
booleanValue(arguments.at(0) == 1 || arguments.at(1) == 1),
|
||||
bv2int(int2bv(arguments.at(0)) | int2bv(arguments.at(1)))
|
||||
);
|
||||
case evmasm::Instruction::XOR:
|
||||
return bv2int(int2bv(arguments.at(0)) ^ int2bv(arguments.at(1)));
|
||||
case evmasm::Instruction::NOT:
|
||||
return smtutil::Expression(u256(-1)) - arguments.at(0);
|
||||
case evmasm::Instruction::SHL:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(0) > 255,
|
||||
constantValue(0),
|
||||
bv2int(int2bv(arguments.at(1)) << int2bv(arguments.at(0)))
|
||||
);
|
||||
case evmasm::Instruction::SHR:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(0) > 255,
|
||||
constantValue(0),
|
||||
bv2int(int2bv(arguments.at(1)) >> int2bv(arguments.at(0)))
|
||||
);
|
||||
case evmasm::Instruction::SAR:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(0) > 255,
|
||||
constantValue(0),
|
||||
bv2int(smtutil::Expression::ashr(int2bv(arguments.at(1)), int2bv(arguments.at(0))))
|
||||
);
|
||||
case evmasm::Instruction::ADDMOD:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(2) == constantValue(0),
|
||||
constantValue(0),
|
||||
(arguments.at(0) + arguments.at(1)) % arguments.at(2)
|
||||
);
|
||||
case evmasm::Instruction::MULMOD:
|
||||
return smtutil::Expression::ite(
|
||||
arguments.at(2) == constantValue(0),
|
||||
constantValue(0),
|
||||
(arguments.at(0) * arguments.at(1)) % arguments.at(2)
|
||||
);
|
||||
// TODO SIGNEXTEND
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return newRestrictedVariable();
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::int2bv(smtutil::Expression _arg) const
|
||||
{
|
||||
return smtutil::Expression::int2bv(std::move(_arg), 256);
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::bv2int(smtutil::Expression _arg) const
|
||||
{
|
||||
return smtutil::Expression::bv2int(std::move(_arg));
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::newVariable()
|
||||
{
|
||||
return m_solver->newVariable(uniqueName(), defaultSort());
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::newRestrictedVariable()
|
||||
{
|
||||
smtutil::Expression var = newVariable();
|
||||
m_solver->addAssertion(0 <= var && var < smtutil::Expression(bigint(1) << 256));
|
||||
return var;
|
||||
}
|
||||
|
||||
string ReasoningBasedSimplifier::uniqueName()
|
||||
{
|
||||
return "expr_" + to_string(m_varCounter++);
|
||||
}
|
||||
|
||||
shared_ptr<Sort> ReasoningBasedSimplifier::defaultSort() const
|
||||
{
|
||||
return SortProvider::intSort();
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::booleanValue(smtutil::Expression _value) const
|
||||
{
|
||||
return smtutil::Expression::ite(_value, constantValue(1), constantValue(0));
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::constantValue(size_t _value) const
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::literalValue(Literal const& _literal) const
|
||||
{
|
||||
return smtutil::Expression(valueOfLiteral(_literal));
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::unsignedToSigned(smtutil::Expression _value)
|
||||
{
|
||||
return smtutil::Expression::ite(
|
||||
_value < smtutil::Expression(bigint(1) << 255),
|
||||
_value,
|
||||
_value - smtutil::Expression(bigint(1) << 256)
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::signedToUnsigned(smtutil::Expression _value)
|
||||
{
|
||||
return smtutil::Expression::ite(
|
||||
_value >= 0,
|
||||
_value,
|
||||
_value + smtutil::Expression(bigint(1) << 256)
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::Expression ReasoningBasedSimplifier::wrap(smtutil::Expression _value)
|
||||
{
|
||||
smtutil::Expression rest = newRestrictedVariable();
|
||||
smtutil::Expression multiplier = newVariable();
|
||||
m_solver->addAssertion(_value == multiplier * smtutil::Expression(bigint(1) << 256) + rest);
|
||||
return rest;
|
||||
}
|
98
libyul/optimiser/ReasoningBasedSimplifier.h
Normal file
98
libyul/optimiser/ReasoningBasedSimplifier.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/Dialect.h>
|
||||
|
||||
// because of instruction
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace solidity::smtutil
|
||||
{
|
||||
class SolverInterface;
|
||||
class Expression;
|
||||
struct Sort;
|
||||
}
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Reasoning-based simplifier.
|
||||
* This optimizer uses SMT solvers to check whether `if` conditions are constant.
|
||||
* - If `constraints AND condition` is UNSAT, the condition is never true and the whole body can be removed.
|
||||
* - If `constraints AND NOT condition` is UNSAT, the condition is always true and can be replaced by `1`.
|
||||
* The simplifications above can only be applied if the condition is movable.
|
||||
*
|
||||
* It is only effective on the EVM dialect, but safe to use on other dialects.
|
||||
*
|
||||
* Prerequisite: Disambiguator, SSATransform.
|
||||
*/
|
||||
class ReasoningBasedSimplifier: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"ReasoningBasedSimplifier"};
|
||||
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||
static std::optional<std::string> invalidInCurrentEnvironment();
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(VariableDeclaration& _varDecl) override;
|
||||
void operator()(If& _if) override;
|
||||
|
||||
private:
|
||||
explicit ReasoningBasedSimplifier(
|
||||
Dialect const& _dialect,
|
||||
std::set<YulString> const& _ssaVariables
|
||||
);
|
||||
|
||||
smtutil::Expression encodeExpression(
|
||||
Expression const& _expression
|
||||
);
|
||||
|
||||
virtual smtutil::Expression encodeEVMBuiltin(
|
||||
evmasm::Instruction _instruction,
|
||||
std::vector<Expression> const& _arguments
|
||||
);
|
||||
|
||||
smtutil::Expression int2bv(smtutil::Expression _arg) const;
|
||||
smtutil::Expression bv2int(smtutil::Expression _arg) const;
|
||||
|
||||
smtutil::Expression newVariable();
|
||||
virtual smtutil::Expression newRestrictedVariable();
|
||||
std::string uniqueName();
|
||||
|
||||
virtual std::shared_ptr<smtutil::Sort> defaultSort() const;
|
||||
virtual smtutil::Expression booleanValue(smtutil::Expression _value) const;
|
||||
virtual smtutil::Expression constantValue(size_t _value) const;
|
||||
virtual smtutil::Expression literalValue(Literal const& _literal) const;
|
||||
virtual smtutil::Expression unsignedToSigned(smtutil::Expression _value);
|
||||
virtual smtutil::Expression signedToUnsigned(smtutil::Expression _value);
|
||||
virtual smtutil::Expression wrap(smtutil::Expression _value);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
std::set<YulString> const& m_ssaVariables;
|
||||
std::unique_ptr<smtutil::SolverInterface> m_solver;
|
||||
std::map<YulString, smtutil::Expression> m_variables;
|
||||
|
||||
size_t m_varCounter = 0;
|
||||
};
|
||||
|
||||
}
|
@ -41,6 +41,7 @@
|
||||
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
|
||||
#include <libyul/optimiser/ForLoopInitRewriter.h>
|
||||
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
|
||||
#include <libyul/optimiser/ReasoningBasedSimplifier.h>
|
||||
#include <libyul/optimiser/Rematerialiser.h>
|
||||
#include <libyul/optimiser/UnusedFunctionParameterPruner.h>
|
||||
#include <libyul/optimiser/UnusedPruner.h>
|
||||
@ -184,6 +185,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
||||
LoopInvariantCodeMotion,
|
||||
NameSimplifier,
|
||||
RedundantAssignEliminator,
|
||||
ReasoningBasedSimplifier,
|
||||
Rematerialiser,
|
||||
SSAReverser,
|
||||
SSATransform,
|
||||
@ -221,6 +223,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
|
||||
{LoadResolver::name, 'L'},
|
||||
{LoopInvariantCodeMotion::name, 'M'},
|
||||
{NameSimplifier::name, 'N'},
|
||||
{ReasoningBasedSimplifier::name, 'R'},
|
||||
{RedundantAssignEliminator::name, 'r'},
|
||||
{Rematerialiser::name, 'm'},
|
||||
{SSAReverser::name, 'V'},
|
||||
@ -266,6 +269,7 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
|
||||
insideLoop = false;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
yulAssert(
|
||||
string(NonStepAbbreviations).find(abbreviation) == string::npos,
|
||||
"Unhandled syntactic element in the abbreviation sequence"
|
||||
@ -275,6 +279,14 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
|
||||
OptimizerException,
|
||||
"'"s + abbreviation + "' is not a valid step abbreviation"
|
||||
);
|
||||
optional<string> invalid = allSteps().at(stepAbbreviationToNameMap().at(abbreviation))->invalidInCurrentEnvironment();
|
||||
assertThrow(
|
||||
!invalid.has_value(),
|
||||
OptimizerException,
|
||||
"'"s + abbreviation + "' is invalid in the current environment: " + *invalid
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets");
|
||||
}
|
||||
|
@ -188,6 +188,7 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False):
|
||||
# Warning (1878): SPDX license identifier not provided in source file. ....
|
||||
# Warning (3420): Source file does not specify required compiler version!
|
||||
test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err"))
|
||||
test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "yul_unimplemented", "err"))
|
||||
|
||||
# white list of ids which are not covered by tests
|
||||
white_ids = {
|
||||
|
@ -124,6 +124,7 @@ function test_solc_behaviour()
|
||||
sed -i.bak -e '/^Warning (3805): This is a pre-release compiler version, please do not use it in production./d' "$stderr_path"
|
||||
sed -i.bak -e 's/\(^[ ]*auxdata: \)0x[0-9a-f]*$/\1AUXDATA REMOVED/' "$stdout_path"
|
||||
sed -i.bak -e 's/ Consider adding "pragma .*$//' "$stderr_path"
|
||||
sed -i.bak -e 's/\(Unimplemented feature error: .* in \).*$/\1FILENAME REMOVED/' "$stderr_path"
|
||||
sed -i.bak -e 's/"version": "[^"]*"/"version": "VERSION REMOVED"/' "$stdout_path"
|
||||
# Remove trailing empty lines. Needs a line break to make OSX sed happy.
|
||||
sed -i.bak -e '1{/^$/d
|
||||
|
1
test/cmdlineTests/yul_unimplemented/args
Normal file
1
test/cmdlineTests/yul_unimplemented/args
Normal file
@ -0,0 +1 @@
|
||||
--ir --error-codes
|
5
test/cmdlineTests/yul_unimplemented/err
Normal file
5
test/cmdlineTests/yul_unimplemented/err
Normal file
@ -0,0 +1,5 @@
|
||||
Error (1834): Unimplemented feature error: setToZero for type t_struct$_S_$4_storage not yet implemented! in FILENAME REMOVED
|
||||
--> yul_unimplemented/input.sol:9:9:
|
||||
|
|
||||
9 | delete str;
|
||||
| ^^^^^^^^^^
|
1
test/cmdlineTests/yul_unimplemented/exit
Normal file
1
test/cmdlineTests/yul_unimplemented/exit
Normal file
@ -0,0 +1 @@
|
||||
1
|
11
test/cmdlineTests/yul_unimplemented/input.sol
Normal file
11
test/cmdlineTests/yul_unimplemented/input.sol
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.0;
|
||||
contract test {
|
||||
struct S {
|
||||
uint x;
|
||||
}
|
||||
S str;
|
||||
constructor() {
|
||||
delete str;
|
||||
}
|
||||
}
|
@ -54,6 +54,12 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
|
||||
m_compiler.setRevertStringBehaviour(m_revertStrings);
|
||||
if (!m_compiler.compile())
|
||||
{
|
||||
// The testing framework expects an exception for
|
||||
// "unimplemented" yul IR generation.
|
||||
if (m_compileViaYul)
|
||||
for (auto const& error: m_compiler.errors())
|
||||
if (error->type() == langutil::Error::Type::CodeGenerationError)
|
||||
BOOST_THROW_EXCEPTION(*error);
|
||||
langutil::SourceReferenceFormatter formatter(std::cerr);
|
||||
|
||||
for (auto const& error: m_compiler.errors())
|
||||
|
14
test/libsolidity/semanticTests/arithmetics/signed_mod.sol
Normal file
14
test/libsolidity/semanticTests/arithmetics/signed_mod.sol
Normal file
@ -0,0 +1,14 @@
|
||||
contract C {
|
||||
function f(int a, int b) public pure returns (int) {
|
||||
return a % b;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(int256,int256): 7, 5 -> 2
|
||||
// f(int256,int256): 7, -5 -> 2
|
||||
// f(int256,int256): -7, 5 -> -2
|
||||
// f(int256,int256): -7, 5 -> -2
|
||||
// f(int256,int256): -5, -5 -> 0
|
@ -0,0 +1,23 @@
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract C {
|
||||
struct S {
|
||||
bool[] b;
|
||||
}
|
||||
|
||||
function f() public returns (uint256, bool[][2] memory, S[2] memory, uint256) {
|
||||
return (
|
||||
5,
|
||||
[new bool[](1), new bool[](2)],
|
||||
[S(new bool[](2)), S(new bool[](5))],
|
||||
6
|
||||
);
|
||||
}
|
||||
|
||||
function g() public returns (uint256, uint256) {
|
||||
(uint256 a, , , uint256 b) = this.f();
|
||||
return (a, b);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// g() -> 5, 6
|
28
test/libsolidity/smtCheckerTests/special/event.sol
Normal file
28
test/libsolidity/smtCheckerTests/special/event.sol
Normal file
@ -0,0 +1,28 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
event Nudge();
|
||||
event SomeArgs(uint, uint);
|
||||
event Caller(address, uint);
|
||||
function f() payable external {
|
||||
emit Nudge();
|
||||
emit SomeArgs(134, 567);
|
||||
emit Caller(msg.sender, msg.value);
|
||||
}
|
||||
function g_data() pure internal returns (uint) {
|
||||
assert(true);
|
||||
}
|
||||
function g() external {
|
||||
emit SomeArgs(g_data(), g_data());
|
||||
}
|
||||
bool x = true;
|
||||
function h_data() view internal returns (uint) {
|
||||
assert(x);
|
||||
}
|
||||
function h() external {
|
||||
x = false;
|
||||
emit SomeArgs(h_data(), h_data());
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 6328: (440-449): Assertion violation happens here.
|
39
test/libsolidity/smtCheckerTests/special/log.sol
Normal file
39
test/libsolidity/smtCheckerTests/special/log.sol
Normal file
@ -0,0 +1,39 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract C {
|
||||
function f() external {
|
||||
bytes32 t1 = bytes32(uint256(0x1234));
|
||||
log0(t1);
|
||||
log1(t1, t1);
|
||||
log2(t1, t1, t1);
|
||||
log3(t1, t1, t1, t1);
|
||||
log4(t1, t1, t1, t1, t1);
|
||||
}
|
||||
function g_data() pure internal returns (bytes32) {
|
||||
assert(true);
|
||||
return bytes32(uint256(0x5678));
|
||||
}
|
||||
function g() external {
|
||||
// To test that the function call is actually visited.
|
||||
log0(g_data());
|
||||
log1(g_data(), g_data());
|
||||
log2(g_data(), g_data(), g_data());
|
||||
log3(g_data(), g_data(), g_data(), g_data());
|
||||
log4(g_data(), g_data(), g_data(), g_data(), g_data());
|
||||
}
|
||||
bool x = true;
|
||||
function h_data() view internal returns (bytes32) {
|
||||
assert(x);
|
||||
}
|
||||
function h() external {
|
||||
// To test that the function call is actually visited.
|
||||
x = false;
|
||||
log0(h_data());
|
||||
log1(h_data(), h_data());
|
||||
log2(h_data(), h_data(), h_data());
|
||||
log3(h_data(), h_data(), h_data(), h_data());
|
||||
log4(h_data(), h_data(), h_data(), h_data(), h_data());
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 6328: (668-677): Assertion violation happens here.
|
@ -0,0 +1,9 @@
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract C {
|
||||
function f() public pure returns(string[5] calldata) {
|
||||
return ["h", "e", "l", "l", "o"];
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 6359: (122-147): Return argument type string memory[5] memory is not implicitly convertible to expected type (type of first return variable) string calldata[5] calldata.
|
@ -0,0 +1,17 @@
|
||||
contract C {
|
||||
function f1() public pure returns(string calldata) {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
function f2() public pure returns(string calldata) {
|
||||
return unicode"hello";
|
||||
}
|
||||
|
||||
function f3() public pure returns(bytes calldata) {
|
||||
return hex"68656c6c6f";
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 6359: (85-92): Return argument type literal_string "hello" is not implicitly convertible to expected type (type of first return variable) string calldata.
|
||||
// TypeError 6359: (173-187): Return argument type literal_string "hello" is not implicitly convertible to expected type (type of first return variable) string calldata.
|
||||
// TypeError 6359: (267-282): Return argument type literal_string "hello" is not implicitly convertible to expected type (type of first return variable) bytes calldata.
|
@ -0,0 +1,14 @@
|
||||
contract C {
|
||||
function g(string calldata _s) public {}
|
||||
function h(bytes calldata _b) public {}
|
||||
|
||||
function f() public {
|
||||
g("hello");
|
||||
g(unicode"hello");
|
||||
h(hex"68656c6c6f");
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 9553: (139-146): Invalid type for argument in function call. Invalid implicit conversion from literal_string "hello" to string calldata requested.
|
||||
// TypeError 9553: (159-173): Invalid type for argument in function call. Invalid implicit conversion from literal_string "hello" to string calldata requested.
|
||||
// TypeError 9553: (186-201): Invalid type for argument in function call. Invalid implicit conversion from literal_string "hello" to bytes calldata requested.
|
@ -53,6 +53,7 @@
|
||||
#include <libyul/optimiser/UnusedPruner.h>
|
||||
#include <libyul/optimiser/ExpressionJoiner.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/optimiser/ReasoningBasedSimplifier.h>
|
||||
#include <libyul/optimiser/SSAReverser.h>
|
||||
#include <libyul/optimiser/SSATransform.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
@ -102,6 +103,9 @@ YulOptimizerTest::YulOptimizerTest(string const& _filename):
|
||||
BOOST_THROW_EXCEPTION(runtime_error("Filename path has to contain a directory: \"" + _filename + "\"."));
|
||||
m_optimizerStep = std::prev(std::prev(path.end()))->string();
|
||||
|
||||
if (m_optimizerStep == "reasoningBasedSimplifier" && solidity::test::CommonOptions::get().disableSMT)
|
||||
m_shouldRun = false;
|
||||
|
||||
m_source = m_reader.source();
|
||||
|
||||
auto dialectName = m_reader.stringSetting("dialect", "evm");
|
||||
@ -320,6 +324,11 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
|
||||
LiteralRematerialiser::run(*m_context, *m_object->code);
|
||||
StructuralSimplifier::run(*m_context, *m_object->code);
|
||||
}
|
||||
else if (m_optimizerStep == "reasoningBasedSimplifier")
|
||||
{
|
||||
disambiguate();
|
||||
ReasoningBasedSimplifier::run(*m_context, *m_object->code);
|
||||
}
|
||||
else if (m_optimizerStep == "equivalentFunctionCombiner")
|
||||
{
|
||||
disambiguate();
|
||||
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
let y := calldataload(32)
|
||||
let z := calldataload(64)
|
||||
let result := addmod(x, y, z)
|
||||
|
||||
// should be zero
|
||||
if gt(result, z) { sstore(0, 1) }
|
||||
|
||||
// addmod is equal to mod of sum for small numbers
|
||||
if and(and(lt(x, 1000), lt(y, 1000)), lt(z, 1000)) {
|
||||
if eq(result, mod(add(x, y), z)) { sstore(0, 9) }
|
||||
}
|
||||
|
||||
// but not in general
|
||||
if and(and(gt(x, sub(0, 5)), gt(y, sub(0, 2))), eq(z, 3)) {
|
||||
if eq(result, mod(add(x, y), z)) { sstore(0, 5) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// let y := calldataload(32)
|
||||
// let z := calldataload(64)
|
||||
// let result := addmod(x, y, z)
|
||||
// if 0 { }
|
||||
// if and(and(lt(x, 1000), lt(y, 1000)), lt(z, 1000)) { if 1 { sstore(0, 9) } }
|
||||
// if and(and(gt(x, sub(0, 5)), gt(y, sub(0, 2))), eq(z, 3)) { if 0 { } }
|
||||
// }
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
let x := 7
|
||||
let y := 8
|
||||
if eq(add(x, y), 15) { }
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := 7
|
||||
// let y := 8
|
||||
// if 1 { }
|
||||
// }
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
function f() -> z
|
||||
{
|
||||
z := 15
|
||||
}
|
||||
let x := 7
|
||||
let y := 8
|
||||
if eq(add(x, y), f()) { }
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// function f() -> z
|
||||
// { z := 15 }
|
||||
// let x := 7
|
||||
// let y := 8
|
||||
// if eq(add(x, y), f()) { }
|
||||
// }
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
function f() -> z
|
||||
{
|
||||
sstore(1, 15)
|
||||
z := 15
|
||||
}
|
||||
let x := 7
|
||||
let y := 8
|
||||
if eq(add(x, y), f()) { }
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// function f() -> z
|
||||
// {
|
||||
// sstore(1, 15)
|
||||
// z := 15
|
||||
// }
|
||||
// let x := 7
|
||||
// let y := 8
|
||||
// if eq(add(x, y), f()) { }
|
||||
// }
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
let vloc_x := calldataload(0)
|
||||
let vloc_y := calldataload(1)
|
||||
if lt(vloc_x, shl(100, 1)) {
|
||||
if lt(vloc_y, shl(100, 1)) {
|
||||
if iszero(and(iszero(iszero(vloc_x)), gt(vloc_y, div(not(0), vloc_x)))) {
|
||||
let vloc := mul(vloc_x, vloc_y)
|
||||
sstore(0, vloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=constantinople
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let vloc_x := calldataload(0)
|
||||
// let vloc_y := calldataload(1)
|
||||
// if lt(vloc_x, shl(100, 1))
|
||||
// {
|
||||
// if lt(vloc_y, shl(100, 1))
|
||||
// {
|
||||
// if 1
|
||||
// {
|
||||
// let vloc := mul(vloc_x, vloc_y)
|
||||
// sstore(0, vloc)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
let y := calldataload(32)
|
||||
let z := calldataload(64)
|
||||
let result := mulmod(x, y, z)
|
||||
|
||||
// should be zero
|
||||
if gt(result, z) { sstore(0, 1) }
|
||||
|
||||
// mulmod is equal to mod of product for small numbers
|
||||
if and(and(lt(x, 1000), lt(y, 1000)), lt(z, 1000)) {
|
||||
if eq(result, mod(mul(x, y), z)) { sstore(0, 9) }
|
||||
}
|
||||
|
||||
// but not in general
|
||||
if and(and(gt(x, sub(0, 5)), eq(y, 2)), eq(z, 3)) {
|
||||
if eq(result, mod(mul(x, y), z)) { sstore(0, 5) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// let y := calldataload(32)
|
||||
// let z := calldataload(64)
|
||||
// let result := mulmod(x, y, z)
|
||||
// if 0 { }
|
||||
// if and(and(lt(x, 1000), lt(y, 1000)), lt(z, 1000)) { if 1 { sstore(0, 9) } }
|
||||
// if and(and(gt(x, sub(0, 5)), eq(y, 2)), eq(z, 3)) { if 0 { } }
|
||||
// }
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
let x := sub(0, 7)
|
||||
let y := 2
|
||||
// (-7)/2 == -3 on the evm
|
||||
if iszero(add(sdiv(x, y), 3)) { }
|
||||
if iszero(add(sdiv(x, y), 4)) { }
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := sub(0, 7)
|
||||
// let y := 2
|
||||
// if 1 { }
|
||||
// if 0 { }
|
||||
// }
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
let x := calldataload(2)
|
||||
let t := lt(x, 20)
|
||||
if t {
|
||||
if lt(x, 21) { }
|
||||
if lt(x, 20) { }
|
||||
if lt(x, 19) { }
|
||||
if gt(x, 20) { }
|
||||
if iszero(gt(x, 20)) { }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(2)
|
||||
// let t := lt(x, 20)
|
||||
// if t
|
||||
// {
|
||||
// if 1 { }
|
||||
// if 1 { }
|
||||
// if lt(x, 19) { }
|
||||
// if 0 { }
|
||||
// if 1 { }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
let y := calldataload(0)
|
||||
let t := calldataload(32)
|
||||
|
||||
if sgt(sub(y, 1), y) {
|
||||
// y - 1 > y, i.e. y is the most negative value
|
||||
if eq(sdiv(y, sub(0, 1)), y) {
|
||||
// should be true: y / -1 == y
|
||||
sstore(0, 7)
|
||||
}
|
||||
if iszero(eq(y, t)) {
|
||||
// t is not the most negative value
|
||||
if eq(sdiv(t, sub(0, 1)), sub(0, t)) {
|
||||
// should be true: t / -1 = 0 - t
|
||||
sstore(1, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let y := calldataload(0)
|
||||
// let t := calldataload(32)
|
||||
// if sgt(sub(y, 1), y)
|
||||
// {
|
||||
// if 1 { sstore(0, 7) }
|
||||
// if iszero(eq(y, t)) { if 1 { sstore(1, 7) } }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,53 @@
|
||||
{
|
||||
// 7 % 5 == 2
|
||||
// 7 % -5 == 2
|
||||
// -7 % 5 == -2
|
||||
// -7 % -5 == -2
|
||||
// -5 % -5 == 0
|
||||
let x := calldataload(0)
|
||||
let y := calldataload(32)
|
||||
let result := smod(x, y)
|
||||
if eq(x, 7) {
|
||||
if eq(y, 5) {
|
||||
if eq(result, 2) { sstore(0, 7)}
|
||||
}
|
||||
if eq(y, sub(0, 5)) {
|
||||
if eq(result, 2) { sstore(0, 7)}
|
||||
}
|
||||
}
|
||||
if eq(x, sub(0, 7)) {
|
||||
if eq(y, 5) {
|
||||
if eq(result, sub(0, 2)) { sstore(0, 7)}
|
||||
}
|
||||
if eq(y, sub(0, 5)) {
|
||||
if eq(result, sub(0, 2)) { sstore(0, 7)}
|
||||
}
|
||||
}
|
||||
if eq(x, sub(0, 5)) {
|
||||
if eq(y, sub(0, 5)) {
|
||||
if eq(result, 0) { sstore(0, 7)}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// let y := calldataload(32)
|
||||
// let result := smod(x, y)
|
||||
// if eq(x, 7)
|
||||
// {
|
||||
// if eq(y, 5) { if 1 { sstore(0, 7) } }
|
||||
// if eq(y, sub(0, 5)) { if 1 { sstore(0, 7) } }
|
||||
// }
|
||||
// if eq(x, sub(0, 7))
|
||||
// {
|
||||
// if eq(y, 5) { if 1 { sstore(0, 7) } }
|
||||
// if eq(y, sub(0, 5)) { if 1 { sstore(0, 7) } }
|
||||
// }
|
||||
// if eq(x, sub(0, 5))
|
||||
// {
|
||||
// if eq(y, sub(0, 5)) { if 1 { sstore(0, 7) } }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,5 @@
|
||||
{ }
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// { }
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
let x := 7
|
||||
let y := 8
|
||||
if gt(sub(x, y), 20) { }
|
||||
if eq(sub(x, y), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {}
|
||||
}
|
||||
// ----
|
||||
// step: reasoningBasedSimplifier
|
||||
//
|
||||
// {
|
||||
// let x := 7
|
||||
// let y := 8
|
||||
// if 1 { }
|
||||
// if 1 { }
|
||||
// }
|
@ -36,6 +36,7 @@
|
||||
#include <libyul/optimiser/StackCompressor.h>
|
||||
#include <libyul/optimiser/VarNameCleaner.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <libyul/optimiser/ReasoningBasedSimplifier.h>
|
||||
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
@ -157,7 +158,7 @@ public:
|
||||
map<char, string> const& extraOptions = {
|
||||
{'#', "quit"},
|
||||
{',', "VarNameCleaner"},
|
||||
{';', "StackCompressor"},
|
||||
{';', "StackCompressor"}
|
||||
};
|
||||
|
||||
printUsageBanner(abbreviationMap, extraOptions, 4);
|
||||
|
@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(output_operator_should_create_concise_and_unambiguous_strin
|
||||
|
||||
BOOST_TEST(chromosome.length() == allSteps.size());
|
||||
BOOST_TEST(chromosome.optimisationSteps() == allSteps);
|
||||
BOOST_TEST(toString(chromosome) == "flcCUnDvejsxIOoighTLMNrmVatpud");
|
||||
BOOST_TEST(toString(chromosome) == "flcCUnDvejsxIOoighTLMNRrmVatpud");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names)
|
||||
|
Loading…
Reference in New Issue
Block a user