mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
@@ -55,7 +55,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
|
||||
checkDuplicateEvents(_contract);
|
||||
m_overrideChecker.check(_contract);
|
||||
checkBaseConstructorArguments(_contract);
|
||||
checkAbstractFunctions(_contract);
|
||||
checkAbstractDefinitions(_contract);
|
||||
checkExternalTypeClashes(_contract);
|
||||
checkHashCollisions(_contract);
|
||||
checkLibraryRequirements(_contract);
|
||||
@@ -156,55 +156,43 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
|
||||
}
|
||||
}
|
||||
|
||||
void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract)
|
||||
void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract)
|
||||
{
|
||||
// Mapping from name to function definition (exactly one per argument type equality class) and
|
||||
// flag to indicate whether it is fully implemented.
|
||||
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
|
||||
map<string, vector<FunTypeAndFlag>> functions;
|
||||
// Collects functions, static variable getters and modifiers. If they
|
||||
// override (unimplemented) base class ones, they are replaced.
|
||||
set<OverrideProxy, OverrideProxy::CompareBySignature> proxies;
|
||||
|
||||
auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented)
|
||||
auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy)
|
||||
{
|
||||
auto& overloads = functions[_declaration.name()];
|
||||
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
|
||||
{
|
||||
return _type->hasEqualParameterTypes(*_funAndFlag.first);
|
||||
});
|
||||
if (it == overloads.end())
|
||||
overloads.emplace_back(_type, _implemented);
|
||||
else if (_implemented)
|
||||
it->second = true;
|
||||
// Overwrite an existing proxy, if it exists.
|
||||
if (!_overrideProxy.unimplemented())
|
||||
proxies.erase(_overrideProxy);
|
||||
|
||||
proxies.insert(_overrideProxy);
|
||||
};
|
||||
|
||||
// Search from base to derived, collect all functions and update
|
||||
// the 'implemented' flag.
|
||||
// Search from base to derived, collect all functions and modifiers and
|
||||
// update proxies.
|
||||
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
|
||||
{
|
||||
for (VariableDeclaration const* v: contract->stateVariables())
|
||||
if (v->isPartOfExternalInterface())
|
||||
registerFunction(*v, TypeProvider::function(*v), true);
|
||||
registerProxy(OverrideProxy(v));
|
||||
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
if (!function->isConstructor())
|
||||
registerFunction(
|
||||
*function,
|
||||
TypeProvider::function(*function)->asCallableFunction(false),
|
||||
function->isImplemented()
|
||||
);
|
||||
registerProxy(OverrideProxy(function));
|
||||
|
||||
for (ModifierDefinition const* modifier: contract->functionModifiers())
|
||||
registerProxy(OverrideProxy(modifier));
|
||||
}
|
||||
|
||||
// Set to not fully implemented if at least one flag is false.
|
||||
// Note that `_contract.annotation().unimplementedFunctions` has already been
|
||||
// Note that `_contract.annotation().unimplementedDeclarations` has already been
|
||||
// pre-filled by `checkBaseConstructorArguments`.
|
||||
for (auto const& it: functions)
|
||||
for (auto const& funAndFlag: it.second)
|
||||
if (!funAndFlag.second)
|
||||
{
|
||||
FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(&funAndFlag.first->declaration());
|
||||
solAssert(function, "");
|
||||
_contract.annotation().unimplementedFunctions.push_back(function);
|
||||
break;
|
||||
}
|
||||
for (auto const& proxy: proxies)
|
||||
if (proxy.unimplemented())
|
||||
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
|
||||
|
||||
if (_contract.abstract())
|
||||
{
|
||||
@@ -221,15 +209,16 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
|
||||
if (
|
||||
_contract.contractKind() == ContractKind::Contract &&
|
||||
!_contract.abstract() &&
|
||||
!_contract.annotation().unimplementedFunctions.empty()
|
||||
!_contract.annotation().unimplementedDeclarations.empty()
|
||||
)
|
||||
{
|
||||
SecondarySourceLocation ssl;
|
||||
for (auto function: _contract.annotation().unimplementedFunctions)
|
||||
ssl.append("Missing implementation:", function->location());
|
||||
for (auto declaration: _contract.annotation().unimplementedDeclarations)
|
||||
ssl.append("Missing implementation: ", declaration->location());
|
||||
m_errorReporter.typeError(_contract.location(), ssl,
|
||||
"Contract \"" + _contract.annotation().canonicalName
|
||||
+ "\" should be marked as abstract.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +266,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
|
||||
if (FunctionDefinition const* constructor = contract->constructor())
|
||||
if (contract != &_contract && !constructor->parameters().empty())
|
||||
if (!_contract.annotation().baseConstructorArguments.count(constructor))
|
||||
_contract.annotation().unimplementedFunctions.push_back(constructor);
|
||||
_contract.annotation().unimplementedDeclarations.push_back(constructor);
|
||||
}
|
||||
|
||||
void ContractLevelChecker::annotateBaseConstructorArguments(
|
||||
|
||||
@@ -61,7 +61,8 @@ private:
|
||||
void checkDuplicateEvents(ContractDefinition const& _contract);
|
||||
template <class T>
|
||||
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
|
||||
void checkAbstractFunctions(ContractDefinition const& _contract);
|
||||
/// Checks for unimplemented functions and modifiers.
|
||||
void checkAbstractDefinitions(ContractDefinition const& _contract);
|
||||
/// Checks that the base constructor arguments are properly provided.
|
||||
/// Fills the list of unimplemented functions in _contract's annotations.
|
||||
void checkBaseConstructorArguments(ContractDefinition const& _contract);
|
||||
|
||||
@@ -148,7 +148,8 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec
|
||||
funcDef->body().accept(*this);
|
||||
}
|
||||
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
|
||||
modDef->body().accept(*this);
|
||||
if (modDef->isImplemented())
|
||||
modDef->body().accept(*this);
|
||||
|
||||
m_currentConstructor = prevConstructor;
|
||||
|
||||
|
||||
@@ -327,6 +327,16 @@ ModifierType const* OverrideProxy::modifierType() const
|
||||
}, m_item);
|
||||
}
|
||||
|
||||
|
||||
Declaration const* OverrideProxy::declaration() const
|
||||
{
|
||||
return std::visit(GenericVisitor{
|
||||
[&](FunctionDefinition const* _function) -> Declaration const* { return _function; },
|
||||
[&](VariableDeclaration const* _variable) -> Declaration const* { return _variable; },
|
||||
[&](ModifierDefinition const* _modifier) -> Declaration const* { return _modifier; }
|
||||
}, m_item);
|
||||
}
|
||||
|
||||
SourceLocation const& OverrideProxy::location() const
|
||||
{
|
||||
return std::visit(GenericVisitor{
|
||||
@@ -365,7 +375,7 @@ bool OverrideProxy::unimplemented() const
|
||||
{
|
||||
return std::visit(GenericVisitor{
|
||||
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
|
||||
[&](ModifierDefinition const*) { return false; },
|
||||
[&](ModifierDefinition const* _item) { return !_item->isImplemented(); },
|
||||
[&](VariableDeclaration const*) { return false; }
|
||||
}, m_item);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ public:
|
||||
FunctionType const* functionType() const;
|
||||
ModifierType const* modifierType() const;
|
||||
|
||||
Declaration const* declaration() const;
|
||||
|
||||
langutil::SourceLocation const& location() const;
|
||||
|
||||
std::string astNodeName() const;
|
||||
|
||||
@@ -141,7 +141,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
|
||||
|
||||
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
|
||||
{
|
||||
if (!m_placeholderFound)
|
||||
if (_modifier.isImplemented() && !m_placeholderFound)
|
||||
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
|
||||
m_placeholderFound = false;
|
||||
}
|
||||
|
||||
@@ -289,6 +289,12 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
|
||||
{
|
||||
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||
m_errorReporter.typeError(_modifier.location(), "Modifiers without implementation must be marked virtual.");
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
bool isLibraryFunction = _function.inContractKind() == ContractKind::Library;
|
||||
@@ -1290,8 +1296,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
|
||||
{
|
||||
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
|
||||
{
|
||||
if (tupleExpression->components().empty())
|
||||
m_errorReporter.typeError(_expression.location(), "Empty tuple on the left hand side.");
|
||||
|
||||
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
|
||||
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
|
||||
auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector<TypePointer> { &_type };
|
||||
|
||||
solAssert(
|
||||
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
|
||||
@@ -2591,6 +2600,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
|
||||
annotation.isPure = true;
|
||||
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId")
|
||||
annotation.isPure = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -112,6 +112,7 @@ private:
|
||||
|
||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||
void endVisit(UsingForDirective const& _usingFor) override;
|
||||
void endVisit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(FunctionDefinition const& _function) override;
|
||||
bool visit(VariableDeclaration const& _variable) override;
|
||||
/// We need to do this manually because we want to pass the bases of the current contract in
|
||||
|
||||
@@ -356,6 +356,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||
{MagicType::Kind::MetaType, "creationCode"},
|
||||
{MagicType::Kind::MetaType, "runtimeCode"},
|
||||
{MagicType::Kind::MetaType, "name"},
|
||||
{MagicType::Kind::MetaType, "interfaceId"},
|
||||
};
|
||||
set<MagicMember> static const payableMembers{
|
||||
{MagicType::Kind::Message, "value"}
|
||||
|
||||
@@ -98,9 +98,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
|
||||
return util::contains(annotation().linearizedBaseContracts, &_base);
|
||||
}
|
||||
|
||||
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
||||
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const
|
||||
{
|
||||
auto exportedFunctionList = interfaceFunctionList();
|
||||
auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions);
|
||||
|
||||
map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
|
||||
for (auto const& it: exportedFunctionList)
|
||||
@@ -176,14 +176,16 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
|
||||
return *m_interfaceEvents;
|
||||
}
|
||||
|
||||
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const
|
||||
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
|
||||
{
|
||||
if (!m_interfaceFunctionList)
|
||||
if (!m_interfaceFunctionList[_includeInheritedFunctions])
|
||||
{
|
||||
set<string> signaturesSeen;
|
||||
m_interfaceFunctionList = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
|
||||
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
|
||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||
{
|
||||
if (_includeInheritedFunctions == false && contract != this)
|
||||
continue;
|
||||
vector<FunctionTypePointer> functions;
|
||||
for (FunctionDefinition const* f: contract->definedFunctions())
|
||||
if (f->isPartOfExternalInterface())
|
||||
@@ -201,12 +203,12 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
|
||||
{
|
||||
signaturesSeen.insert(functionSignature);
|
||||
util::FixedHash<4> hash(util::keccak256(functionSignature));
|
||||
m_interfaceFunctionList->emplace_back(hash, fun);
|
||||
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return *m_interfaceFunctionList;
|
||||
return *m_interfaceFunctionList[_includeInheritedFunctions];
|
||||
}
|
||||
|
||||
TypePointer ContractDefinition::type() const
|
||||
|
||||
@@ -489,8 +489,8 @@ public:
|
||||
|
||||
/// @returns a map of canonical function signatures to FunctionDefinitions
|
||||
/// as intended for use by the ABI.
|
||||
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions() const;
|
||||
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList() const;
|
||||
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
|
||||
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;
|
||||
|
||||
/// @returns a list of all declarations in this contract
|
||||
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
|
||||
@@ -529,7 +529,7 @@ private:
|
||||
ContractKind m_contractKind;
|
||||
bool m_abstract{false};
|
||||
|
||||
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
|
||||
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
|
||||
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||
};
|
||||
|
||||
@@ -969,7 +969,7 @@ private:
|
||||
/**
|
||||
* Definition of a function modifier.
|
||||
*/
|
||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
|
||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
|
||||
{
|
||||
public:
|
||||
ModifierDefinition(
|
||||
@@ -980,18 +980,19 @@ public:
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
bool _isVirtual,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||
ASTPointer<Block> _body
|
||||
ASTPointer<Block> const& _body
|
||||
):
|
||||
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_body(std::move(_body))
|
||||
ImplementationOptional(_body != nullptr),
|
||||
m_body(_body)
|
||||
{
|
||||
}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
Block const& body() const { return *m_body; }
|
||||
Block const& body() const { solAssert(m_body, ""); return *m_body; }
|
||||
|
||||
TypePointer type() const override;
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
|
||||
|
||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||
{
|
||||
/// List of functions without a body. Can also contain functions from base classes.
|
||||
std::vector<FunctionDefinition const*> unimplementedFunctions;
|
||||
/// List of functions and modifiers without a body. Can also contain functions from base classes.
|
||||
std::vector<Declaration const*> unimplementedDeclarations;
|
||||
/// List of all (direct and indirect) base contracts in order from derived to
|
||||
/// base, including the contract itself.
|
||||
std::vector<ContractDefinition const*> linearizedBaseContracts;
|
||||
|
||||
@@ -272,7 +272,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
||||
make_pair("contractKind", contractKind(_node.contractKind())),
|
||||
make_pair("abstract", _node.abstract()),
|
||||
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
||||
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
|
||||
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
|
||||
make_pair("baseContracts", toJson(_node.baseContracts())),
|
||||
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
|
||||
@@ -407,7 +407,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
||||
make_pair("parameters", toJson(_node.parameterList())),
|
||||
make_pair("virtual", _node.markedVirtual()),
|
||||
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
|
||||
make_pair("body", toJson(_node.body()))
|
||||
make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue)
|
||||
};
|
||||
if (!_node.annotation().baseFunctions.empty())
|
||||
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
|
||||
|
||||
@@ -453,7 +453,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
|
||||
createParameterList(member(_node, "parameters")),
|
||||
memberAsBool(_node, "virtual"),
|
||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||
createBlock(member(_node, "body"))
|
||||
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -288,7 +288,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
|
||||
m_parameters->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
m_body->accept(_visitor);
|
||||
if (m_body)
|
||||
m_body->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
@@ -302,7 +303,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
m_parameters->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
m_body->accept(_visitor);
|
||||
if (m_body)
|
||||
m_body->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
@@ -3774,7 +3774,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
||||
{"name", TypeProvider::stringMemory()},
|
||||
});
|
||||
else
|
||||
return {};
|
||||
return MemberList::MemberMap({
|
||||
{"interfaceId", TypeProvider::fixedBytes(4)},
|
||||
});
|
||||
}
|
||||
}
|
||||
solAssert(false, "Unknown kind of magic.");
|
||||
|
||||
@@ -1390,7 +1390,6 @@ public:
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||
private:
|
||||
|
||||
@@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
|
||||
&meter,
|
||||
_object,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
_optimiserSettings.yulOptimiserSteps,
|
||||
_externalIdentifiers
|
||||
);
|
||||
|
||||
|
||||
@@ -1582,6 +1582,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
utils().storeStringData(contract.name());
|
||||
}
|
||||
else if (member == "interfaceId")
|
||||
{
|
||||
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||
uint64_t result{0};
|
||||
for (auto const& function: contract.interfaceFunctionList(false))
|
||||
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||
m_context << (u256{result} << (256 - 32));
|
||||
}
|
||||
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
|
||||
{
|
||||
// no-op
|
||||
|
||||
@@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocationTemporaryMemoryFunction()
|
||||
{
|
||||
string functionName = "allocateTemporaryMemory";
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>() -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::releaseTemporaryMemoryFunction()
|
||||
{
|
||||
string functionName = "releaseTemporaryMemory";
|
||||
return m_functionCollector.createFunction(functionName, [&](){
|
||||
return Whiskers(R"(
|
||||
function <functionName>() {
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
|
||||
{
|
||||
if (_type.baseType()->hasSimpleZeroValueInMemory())
|
||||
@@ -2334,3 +2362,39 @@ string YulUtilFunctions::extractReturndataFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
|
||||
ContractDefinition const& _contract,
|
||||
string const& _creationObjectName
|
||||
)
|
||||
{
|
||||
string functionName = "copy_arguments_for_constructor_" +
|
||||
toString(_contract.constructor()->id()) +
|
||||
"_object_" +
|
||||
_contract.name() +
|
||||
"_" +
|
||||
toString(_contract.id());
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
string returnParams = suffixedVariableNameList("ret_param_",0, _contract.constructor()->parameters().size());
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector);
|
||||
|
||||
return util::Whiskers(R"(
|
||||
function <functionName>() -> <retParams> {
|
||||
let programSize := datasize("<object>")
|
||||
let argSize := sub(codesize(), programSize)
|
||||
|
||||
let memoryDataOffset := <allocate>(argSize)
|
||||
codecopy(memoryDataOffset, programSize, argSize)
|
||||
|
||||
<retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("retParams", returnParams)
|
||||
("object", _creationObjectName)
|
||||
("allocate", allocationFunction())
|
||||
("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,13 @@ public:
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// @returns the name of the function that allocates temporary memory with predefined size
|
||||
/// Return value: pointer
|
||||
std::string allocationTemporaryMemoryFunction();
|
||||
|
||||
/// @returns the name of the function that releases previously allocated temporary memory
|
||||
std::string releaseTemporaryMemoryFunction();
|
||||
|
||||
/// @returns the name of a function that zeroes an array.
|
||||
/// signature: (dataStart, dataSizeInBytes) ->
|
||||
std::string zeroMemoryArrayFunction(ArrayType const& _type);
|
||||
@@ -337,6 +344,13 @@ public:
|
||||
/// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead.
|
||||
std::string extractReturndataFunction();
|
||||
|
||||
/// @returns function name that returns constructor arguments copied to memory
|
||||
/// signature: () -> arguments
|
||||
std::string copyConstructorArgumentsToMemoryFunction(
|
||||
ContractDefinition const& _contract,
|
||||
std::string const& _creationObjectName
|
||||
);
|
||||
|
||||
private:
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
@@ -96,6 +97,15 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
|
||||
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
}
|
||||
|
||||
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
|
||||
{
|
||||
return _contract.name() + "_" + toString(_contract.id());
|
||||
}
|
||||
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
|
||||
{
|
||||
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
|
||||
}
|
||||
|
||||
string IRGenerationContext::newYulVariable()
|
||||
{
|
||||
return "_" + to_string(++m_varCounter);
|
||||
@@ -122,7 +132,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
<#cases>
|
||||
case <funID>
|
||||
{
|
||||
<out> := <name>(<in>)
|
||||
<out> <assignment_op> <name>(<in>)
|
||||
}
|
||||
</cases>
|
||||
default { invalid() }
|
||||
@@ -133,7 +143,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
|
||||
templ("in", suffixedVariableNameList("in_", 0, _in));
|
||||
templ("arrow", _out > 0 ? "->" : "");
|
||||
templ("assignment_op", _out > 0 ? ":=" : "");
|
||||
templ("out", suffixedVariableNameList("out_", 0, _out));
|
||||
|
||||
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
|
||||
// We're not generating code for internal library functions here even though it's possible
|
||||
// to call them via pointers. Right now such calls end up triggering the `default` case in
|
||||
// the switch above.
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
@@ -164,6 +180,11 @@ YulUtilFunctions IRGenerationContext::utils()
|
||||
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
|
||||
}
|
||||
|
||||
ABIFunctions IRGenerationContext::abiFunctions()
|
||||
{
|
||||
return ABIFunctions(m_evmVersion, m_revertStrings, m_functions);
|
||||
}
|
||||
|
||||
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
|
||||
{
|
||||
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
@@ -38,11 +39,8 @@
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class ContractDefinition;
|
||||
class VariableDeclaration;
|
||||
class FunctionDefinition;
|
||||
class Expression;
|
||||
class YulUtilFunctions;
|
||||
class ABIFunctions;
|
||||
|
||||
/**
|
||||
* Class that contains contextual information during IR generation.
|
||||
@@ -93,6 +91,9 @@ public:
|
||||
std::string functionName(FunctionDefinition const& _function);
|
||||
std::string functionName(VariableDeclaration const& _varDecl);
|
||||
|
||||
std::string creationObjectName(ContractDefinition const& _contract) const;
|
||||
std::string runtimeObjectName(ContractDefinition const& _contract) const;
|
||||
|
||||
std::string newYulVariable();
|
||||
|
||||
std::string internalDispatch(size_t _in, size_t _out);
|
||||
@@ -102,6 +103,8 @@ public:
|
||||
|
||||
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
|
||||
|
||||
ABIFunctions abiFunctions();
|
||||
|
||||
/// @returns code that stores @param _message for revert reason
|
||||
/// if m_revertStrings is debug.
|
||||
std::string revertReasonIfDebug(std::string const& _message = "");
|
||||
@@ -112,6 +115,8 @@ public:
|
||||
/// function call that was invoked as part of the try statement.
|
||||
std::string trySuccessConditionVariable(Expression const& _expression) const;
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
@@ -131,6 +136,8 @@ private:
|
||||
/// long as the order of Yul functions in the generated code is deterministic and the same on
|
||||
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
|
||||
std::set<FunctionDefinition const*> m_functionGenerationQueue;
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -48,9 +48,12 @@ using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
||||
pair<string, string> IRGenerator::run(
|
||||
ContractDefinition const& _contract,
|
||||
map<ContractDefinition const*, string const> const& _otherYulSources
|
||||
)
|
||||
{
|
||||
string const ir = yul::reindent(generate(_contract));
|
||||
string const ir = yul::reindent(generate(_contract, _otherYulSources));
|
||||
|
||||
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
if (!asmStack.parseAndAnalyze("", ir))
|
||||
@@ -73,15 +76,28 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
||||
return {warning + ir, warning + asmStack.print()};
|
||||
}
|
||||
|
||||
string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
string IRGenerator::generate(
|
||||
ContractDefinition const& _contract,
|
||||
map<ContractDefinition const*, string const> const& _otherYulSources
|
||||
)
|
||||
{
|
||||
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
|
||||
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
|
||||
{
|
||||
std::string subObjectsSources;
|
||||
for (ContractDefinition const* subObject: subObjects)
|
||||
subObjectsSources += _otherYulSources.at(subObject);
|
||||
return subObjectsSources;
|
||||
};
|
||||
|
||||
Whiskers t(R"(
|
||||
object "<CreationObject>" {
|
||||
code {
|
||||
<memoryInit>
|
||||
<constructor>
|
||||
<callValueCheck>
|
||||
<?notLibrary>
|
||||
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
|
||||
<implicitConstructor>(<constructorParams>)
|
||||
</notLibrary>
|
||||
<deploy>
|
||||
<functions>
|
||||
}
|
||||
@@ -91,24 +107,46 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
<dispatch>
|
||||
<runtimeFunctions>
|
||||
}
|
||||
<runtimeSubObjects>
|
||||
}
|
||||
<subObjects>
|
||||
}
|
||||
)");
|
||||
|
||||
resetContext(_contract);
|
||||
|
||||
t("CreationObject", creationObjectName(_contract));
|
||||
t("CreationObject", m_context.creationObjectName(_contract));
|
||||
t("memoryInit", memoryInit());
|
||||
t("constructor", constructorCode(_contract));
|
||||
t("notLibrary", !_contract.isLibrary());
|
||||
|
||||
FunctionDefinition const* constructor = _contract.constructor();
|
||||
t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
|
||||
vector<string> constructorParams;
|
||||
if (constructor && !constructor->parameters().empty())
|
||||
{
|
||||
for (size_t i = 0; i < constructor->parameters().size(); ++i)
|
||||
constructorParams.emplace_back(m_context.newYulVariable());
|
||||
t(
|
||||
"copyConstructorArguments",
|
||||
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
|
||||
);
|
||||
}
|
||||
t("constructorParams", joinHumanReadable(constructorParams));
|
||||
t("constructorHasParams", !constructorParams.empty());
|
||||
t("implicitConstructor", implicitConstructorName(_contract));
|
||||
|
||||
t("deploy", deployCode(_contract));
|
||||
generateImplicitConstructors(_contract);
|
||||
generateQueuedFunctions();
|
||||
t("functions", m_context.functionCollector().requestedFunctions());
|
||||
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
resetContext(_contract);
|
||||
t("RuntimeObject", runtimeObjectName(_contract));
|
||||
t("RuntimeObject", m_context.runtimeObjectName(_contract));
|
||||
t("dispatch", dispatchRoutine(_contract));
|
||||
generateQueuedFunctions();
|
||||
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
return t.render();
|
||||
}
|
||||
|
||||
@@ -238,64 +276,116 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
string IRGenerator::constructorCode(ContractDefinition const& _contract)
|
||||
pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstructorArguments(
|
||||
ContractDefinition const& _contract
|
||||
)
|
||||
{
|
||||
// Initialization of state variables in base-to-derived order.
|
||||
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
|
||||
map<ContractDefinition const*, string> constructorParams;
|
||||
vector<pair<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *>> baseConstructorArguments;
|
||||
|
||||
using boost::adaptors::reverse;
|
||||
for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts())
|
||||
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
|
||||
base->name().annotation().referencedDeclaration
|
||||
)->constructor(); baseConstructor && base->arguments())
|
||||
baseConstructorArguments.emplace_back(
|
||||
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
|
||||
base->arguments()
|
||||
);
|
||||
|
||||
ostringstream out;
|
||||
if (FunctionDefinition const* constructor = _contract.constructor())
|
||||
for (auto const& modifier: constructor->modifiers())
|
||||
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
|
||||
modifier->name()->annotation().referencedDeclaration
|
||||
)->constructor(); baseConstructor && modifier->arguments())
|
||||
baseConstructorArguments.emplace_back(
|
||||
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
|
||||
modifier->arguments()
|
||||
);
|
||||
|
||||
FunctionDefinition const* constructor = _contract.constructor();
|
||||
if (constructor && !constructor->isPayable())
|
||||
out << callValueCheck();
|
||||
|
||||
for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts))
|
||||
IRGeneratorForStatements generator{m_context, m_utils};
|
||||
for (auto&& [baseContract, arguments]: baseConstructorArguments)
|
||||
{
|
||||
out <<
|
||||
"\n// Begin state variable initialization for contract \"" <<
|
||||
contract->name() <<
|
||||
"\" (" <<
|
||||
contract->stateVariables().size() <<
|
||||
" variables)\n";
|
||||
|
||||
IRGeneratorForStatements generator{m_context, m_utils};
|
||||
for (VariableDeclaration const* variable: contract->stateVariables())
|
||||
if (!variable->isConstant() && !variable->immutable())
|
||||
generator.initializeStateVar(*variable);
|
||||
out << generator.code();
|
||||
|
||||
out << "// End state variable initialization for contract \"" << contract->name() << "\".\n";
|
||||
solAssert(baseContract && arguments, "");
|
||||
if (baseContract->constructor() && !arguments->empty())
|
||||
{
|
||||
vector<string> params;
|
||||
for (size_t i = 0; i < arguments->size(); ++i)
|
||||
params.emplace_back(generator.evaluateExpression(
|
||||
*(arguments->at(i)),
|
||||
*(baseContract->constructor()->parameters()[i]->type())
|
||||
).commaSeparatedList());
|
||||
constructorParams[baseContract] = joinHumanReadable(params);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor)
|
||||
return {generator.code(), constructorParams};
|
||||
}
|
||||
|
||||
string IRGenerator::initStateVariables(ContractDefinition const& _contract)
|
||||
{
|
||||
IRGeneratorForStatements generator{m_context, m_utils};
|
||||
for (VariableDeclaration const* variable: _contract.stateVariables())
|
||||
if (!variable->isConstant() && !variable->immutable())
|
||||
generator.initializeStateVar(*variable);
|
||||
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
|
||||
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
|
||||
{
|
||||
auto listAllParams = [&](
|
||||
map<ContractDefinition const*, string> const& baseParams) -> string
|
||||
{
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
||||
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
|
||||
vector<string> params;
|
||||
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
if (baseParams.count(contract))
|
||||
params.emplace_back(baseParams.at(contract));
|
||||
return joinHumanReadable(params);
|
||||
};
|
||||
|
||||
Whiskers t(R"X(
|
||||
let programSize := datasize("<object>")
|
||||
let argSize := sub(codesize(), programSize)
|
||||
map<ContractDefinition const*, string> baseConstructorParams;
|
||||
for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
|
||||
{
|
||||
ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
|
||||
baseConstructorParams.erase(contract);
|
||||
|
||||
let memoryDataOffset := <allocate>(argSize)
|
||||
codecopy(memoryDataOffset, programSize, argSize)
|
||||
m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() {
|
||||
Whiskers t(R"(
|
||||
function <functionName>(<params><comma><baseParams>) {
|
||||
<evalBaseArguments>
|
||||
<?hasNextConstructor> <nextConstructor>(<nextParams>) </hasNextConstructor>
|
||||
<initStateVariables>
|
||||
<userDefinedConstructorBody>
|
||||
}
|
||||
)");
|
||||
string params;
|
||||
if (contract->constructor())
|
||||
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
|
||||
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
|
||||
t("params", params);
|
||||
string baseParamsString = listAllParams(baseConstructorParams);
|
||||
t("baseParams", baseParamsString);
|
||||
t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
|
||||
t("functionName", implicitConstructorName(*contract));
|
||||
pair<string, map<ContractDefinition const*, string>> evaluatedArgs = evaluateConstructorArguments(*contract);
|
||||
baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
|
||||
t("evalBaseArguments", evaluatedArgs.first);
|
||||
if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
|
||||
{
|
||||
t("hasNextConstructor", true);
|
||||
ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
|
||||
t("nextConstructor", implicitConstructorName(*nextContract));
|
||||
t("nextParams", listAllParams(baseConstructorParams));
|
||||
}
|
||||
else
|
||||
t("hasNextConstructor", false);
|
||||
t("initStateVariables", initStateVariables(*contract));
|
||||
t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : "");
|
||||
|
||||
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
|
||||
|
||||
<constructorName>(<params>)
|
||||
)X");
|
||||
t("object", creationObjectName(_contract));
|
||||
t("allocate", m_utils.allocationFunction());
|
||||
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
|
||||
t("params", suffixedVariableNameList("param_", 0, paramVars));
|
||||
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
|
||||
t("constructorName", m_context.enqueueFunctionForCodeGeneration(*constructor));
|
||||
|
||||
out << t.render();
|
||||
return t.render();
|
||||
});
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||
@@ -304,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||
codecopy(0, dataoffset("<object>"), datasize("<object>"))
|
||||
return(0, datasize("<object>"))
|
||||
)X");
|
||||
t("object", runtimeObjectName(_contract));
|
||||
t("object", m_context.runtimeObjectName(_contract));
|
||||
return t.render();
|
||||
}
|
||||
|
||||
@@ -313,14 +403,9 @@ string IRGenerator::callValueCheck()
|
||||
return "if callvalue() { revert(0, 0) }";
|
||||
}
|
||||
|
||||
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
|
||||
string IRGenerator::implicitConstructorName(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id());
|
||||
}
|
||||
|
||||
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
|
||||
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
}
|
||||
|
||||
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
|
||||
@@ -50,10 +50,16 @@ public:
|
||||
|
||||
/// Generates and returns the IR code, in unoptimized and optimized form
|
||||
/// (or just pretty-printed, depending on the optimizer settings).
|
||||
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
|
||||
std::pair<std::string, std::string> run(
|
||||
ContractDefinition const& _contract,
|
||||
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
|
||||
);
|
||||
|
||||
private:
|
||||
std::string generate(ContractDefinition const& _contract);
|
||||
std::string generate(
|
||||
ContractDefinition const& _contract,
|
||||
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
|
||||
);
|
||||
std::string generate(Block const& _block);
|
||||
|
||||
/// Generates code for all the functions from the function generation queue.
|
||||
@@ -67,12 +73,27 @@ private:
|
||||
/// Generates code that assigns the initial value of the respective type.
|
||||
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
|
||||
|
||||
std::string constructorCode(ContractDefinition const& _contract);
|
||||
/// Generates implicit constructors for all contracts in the inheritance hierarchy of
|
||||
/// @a _contract
|
||||
/// If there are user defined constructors, their body will be included in implicit constructors body.
|
||||
void generateImplicitConstructors(ContractDefinition const& _contract);
|
||||
|
||||
/// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of
|
||||
/// @a _contract
|
||||
/// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params
|
||||
std::pair<std::string, std::map<ContractDefinition const*, std::string>> evaluateConstructorArguments(
|
||||
ContractDefinition const& _contract
|
||||
);
|
||||
|
||||
/// Initializes state variables of
|
||||
/// @a _contract
|
||||
/// @returns Source code to initialize state variables
|
||||
std::string initStateVariables(ContractDefinition const& _contract);
|
||||
|
||||
std::string deployCode(ContractDefinition const& _contract);
|
||||
std::string callValueCheck();
|
||||
|
||||
std::string creationObjectName(ContractDefinition const& _contract);
|
||||
std::string runtimeObjectName(ContractDefinition const& _contract);
|
||||
std::string implicitConstructorName(ContractDefinition const& _contract);
|
||||
|
||||
std::string dispatchRoutine(ContractDefinition const& _contract);
|
||||
|
||||
|
||||
@@ -169,6 +169,14 @@ void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _va
|
||||
assign(m_context.localVariable(_varDecl), zero);
|
||||
}
|
||||
|
||||
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||
define(variable, _expression);
|
||||
return variable;
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
||||
{
|
||||
if (Expression const* expression = _varDeclStatement.initialValue())
|
||||
@@ -559,9 +567,20 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
|
||||
}
|
||||
|
||||
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
|
||||
if (auto expressionType = dynamic_cast<TypeType const*>(memberAccess->expression().annotation().type))
|
||||
if (auto contractType = dynamic_cast<ContractType const*>(expressionType->actualType()))
|
||||
solUnimplementedAssert(
|
||||
!contractType->contractDefinition().isLibrary() || functionType->kind() == FunctionType::Kind::Internal,
|
||||
"Only internal function calls implemented for libraries"
|
||||
);
|
||||
|
||||
solUnimplementedAssert(!functionType->bound(), "");
|
||||
switch (functionType->kind())
|
||||
{
|
||||
case FunctionType::Kind::Declaration:
|
||||
solAssert(false, "Attempted to generate code for calling a function definition.");
|
||||
break;
|
||||
case FunctionType::Kind::Internal:
|
||||
{
|
||||
vector<string> args;
|
||||
@@ -571,32 +590,60 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
else
|
||||
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
|
||||
|
||||
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||
optional<FunctionDefinition const*> functionDef;
|
||||
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
|
||||
{
|
||||
solAssert(!functionType->bound(), "");
|
||||
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||
solUnimplementedAssert(!functionType->bound(), "Internal calls to bound functions are not yet implemented for libraries and not allowed for contracts");
|
||||
|
||||
functionDef = dynamic_cast<FunctionDefinition const*>(memberAccess->annotation().referencedDeclaration);
|
||||
if (functionDef.value() != nullptr)
|
||||
solAssert(functionType->declaration() == *memberAccess->annotation().referencedDeclaration, "");
|
||||
else
|
||||
{
|
||||
define(_functionCall) <<
|
||||
m_context.enqueueFunctionForCodeGeneration(
|
||||
functionDef->resolveVirtual(m_context.mostDerivedContract())
|
||||
) <<
|
||||
"(" <<
|
||||
joinHumanReadable(args) <<
|
||||
")\n";
|
||||
return;
|
||||
solAssert(dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration), "");
|
||||
solAssert(!functionType->hasDeclaration(), "");
|
||||
}
|
||||
}
|
||||
else if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||
{
|
||||
solAssert(!functionType->bound(), "");
|
||||
|
||||
define(_functionCall) <<
|
||||
// NOTE: internalDispatch() takes care of adding the function to function generation queue
|
||||
m_context.internalDispatch(
|
||||
TupleType(functionType->parameterTypes()).sizeOnStack(),
|
||||
TupleType(functionType->returnParameterTypes()).sizeOnStack()
|
||||
) <<
|
||||
"(" <<
|
||||
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
|
||||
joinHumanReadablePrefixed(args) <<
|
||||
")\n";
|
||||
if (auto unresolvedFunctionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||
{
|
||||
functionDef = &unresolvedFunctionDef->resolveVirtual(m_context.mostDerivedContract());
|
||||
solAssert(functionType->declaration() == *identifier->annotation().referencedDeclaration, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
functionDef = nullptr;
|
||||
solAssert(dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration), "");
|
||||
solAssert(!functionType->hasDeclaration(), "");
|
||||
}
|
||||
}
|
||||
else
|
||||
// Not a simple expression like x or A.x
|
||||
functionDef = nullptr;
|
||||
|
||||
solAssert(functionDef.has_value(), "");
|
||||
solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), "");
|
||||
|
||||
if (functionDef.value() != nullptr)
|
||||
define(_functionCall) <<
|
||||
m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) <<
|
||||
"(" <<
|
||||
joinHumanReadable(args) <<
|
||||
")\n";
|
||||
else
|
||||
define(_functionCall) <<
|
||||
// NOTE: internalDispatch() takes care of adding the function to function generation queue
|
||||
m_context.internalDispatch(
|
||||
TupleType(functionType->parameterTypes()).sizeOnStack(),
|
||||
TupleType(functionType->returnParameterTypes()).sizeOnStack()
|
||||
) <<
|
||||
"(" <<
|
||||
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
|
||||
joinHumanReadablePrefixed(args) <<
|
||||
")\n";
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::External:
|
||||
@@ -716,6 +763,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
"))\n";
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::ECRecover:
|
||||
case FunctionType::Kind::SHA256:
|
||||
case FunctionType::Kind::RIPEMD160:
|
||||
{
|
||||
solAssert(!_functionCall.annotation().tryCall, "");
|
||||
appendExternalFunctionCall(_functionCall, arguments);
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::ArrayPop:
|
||||
{
|
||||
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression();
|
||||
@@ -759,6 +814,100 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::MetaType:
|
||||
{
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::GasLeft:
|
||||
{
|
||||
define(_functionCall) << "gas()\n";
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::Selfdestruct:
|
||||
{
|
||||
solAssert(arguments.size() == 1, "");
|
||||
define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n";
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::Log0:
|
||||
case FunctionType::Kind::Log1:
|
||||
case FunctionType::Kind::Log2:
|
||||
case FunctionType::Kind::Log3:
|
||||
case FunctionType::Kind::Log4:
|
||||
{
|
||||
unsigned logNumber = int(functionType->kind()) - int(FunctionType::Kind::Log0);
|
||||
solAssert(arguments.size() == logNumber + 1, "");
|
||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||
string indexedArgs;
|
||||
for (unsigned arg = 0; arg < logNumber; ++arg)
|
||||
indexedArgs += ", " + expressionAsType(*arguments[arg + 1], *(parameterTypes[arg + 1]));
|
||||
Whiskers templ(R"({
|
||||
let <pos> := <freeMemory>
|
||||
let <end> := <encode>(<pos>, <nonIndexedArgs>)
|
||||
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
|
||||
})");
|
||||
templ("pos", m_context.newYulVariable());
|
||||
templ("end", m_context.newYulVariable());
|
||||
templ("freeMemory", freeMemory());
|
||||
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()}));
|
||||
templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList());
|
||||
templ("log", "log" + to_string(logNumber));
|
||||
templ("indexedArgs", indexedArgs);
|
||||
m_code << templ.render();
|
||||
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::Creation:
|
||||
{
|
||||
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
|
||||
solAssert(
|
||||
functionType->returnParameterTypes().size() == 1,
|
||||
"Constructor should return only one type"
|
||||
);
|
||||
|
||||
TypePointers argumentTypes;
|
||||
string constructorParams;
|
||||
for (ASTPointer<Expression const> const& arg: arguments)
|
||||
{
|
||||
argumentTypes.push_back(arg->annotation().type);
|
||||
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
|
||||
}
|
||||
|
||||
ContractDefinition const* contract =
|
||||
&dynamic_cast<ContractType const&>(*functionType->returnParameterTypes().front()).contractDefinition();
|
||||
m_context.subObjectsCreated().insert(contract);
|
||||
|
||||
Whiskers t(R"(
|
||||
let <memPos> := <allocateTemporaryMemory>()
|
||||
let <memEnd> := add(<memPos>, datasize("<object>"))
|
||||
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
|
||||
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
|
||||
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
|
||||
<?saltSet>
|
||||
let <retVars> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
|
||||
<!saltSet>
|
||||
let <retVars> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
|
||||
</saltSet>
|
||||
<releaseTemporaryMemory>()
|
||||
)");
|
||||
t("memPos", m_context.newYulVariable());
|
||||
t("memEnd", m_context.newYulVariable());
|
||||
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
|
||||
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
|
||||
t("object", m_context.creationObjectName(*contract));
|
||||
t("abiEncode",
|
||||
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
|
||||
);
|
||||
t("constructorParams", constructorParams);
|
||||
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
|
||||
t("saltSet", functionType->saltSet());
|
||||
if (functionType->saltSet())
|
||||
t("salt", IRVariable(_functionCall.expression()).part("salt").name());
|
||||
t("retVars", IRVariable(_functionCall).commaSeparatedList());
|
||||
m_code << t.render();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
|
||||
}
|
||||
@@ -916,6 +1065,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
solUnimplementedAssert(false, "");
|
||||
}
|
||||
else if (member == "interfaceId")
|
||||
{
|
||||
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||
uint64_t result{0};
|
||||
for (auto const& function: contract.interfaceFunctionList(false))
|
||||
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
|
||||
}
|
||||
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
|
||||
{
|
||||
// no-op
|
||||
@@ -1069,7 +1227,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
solAssert(holds_alternative<yul::Block>(modified), "");
|
||||
|
||||
// Do not provide dialect so that we get the full type information.
|
||||
m_code << yul::AsmPrinter()(std::get<yul::Block>(std::move(modified))) << "\n";
|
||||
m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1271,9 +1429,9 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
handleVariableReference(*varDecl, _identifier);
|
||||
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
else if (dynamic_cast<ContractDefinition const*>(declaration))
|
||||
{
|
||||
solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
|
||||
// no-op
|
||||
}
|
||||
else if (dynamic_cast<EventDefinition const*>(declaration))
|
||||
{
|
||||
@@ -1365,7 +1523,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
for (auto const& arg: _arguments)
|
||||
{
|
||||
argumentTypes.emplace_back(&type(*arg));
|
||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||
if (IRVariable(*arg).type().sizeOnStack() > 0)
|
||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||
}
|
||||
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
||||
|
||||
@@ -1383,7 +1542,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
|
||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||
|
||||
solUnimplementedAssert(!funType.isBareCall(), "");
|
||||
Whiskers templ(R"(
|
||||
<?checkExistence>
|
||||
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
||||
@@ -1391,8 +1549,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
|
||||
// storage for arguments and returned data
|
||||
let <pos> := <freeMemory>
|
||||
mstore(<pos>, <shl28>(<funId>))
|
||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||
<?bareCall>
|
||||
<!bareCall>
|
||||
mstore(<pos>, <shl28>(<funId>))
|
||||
</bareCall>
|
||||
let <end> := <encodeArgs>(
|
||||
<?bareCall>
|
||||
<pos>
|
||||
<!bareCall>
|
||||
add(<pos>, 4)
|
||||
</bareCall>
|
||||
<argumentString>
|
||||
)
|
||||
|
||||
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
<?noTryCall>
|
||||
@@ -1414,14 +1582,25 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
)");
|
||||
templ("pos", m_context.newYulVariable());
|
||||
templ("end", m_context.newYulVariable());
|
||||
templ("bareCall", funType.isBareCall());
|
||||
if (_functionCall.annotation().tryCall)
|
||||
templ("success", m_context.trySuccessConditionVariable(_functionCall));
|
||||
else
|
||||
templ("success", m_context.newYulVariable());
|
||||
templ("freeMemory", freeMemory());
|
||||
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
||||
|
||||
if (!funType.isBareCall())
|
||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||
|
||||
if (funKind == FunctionType::Kind::ECRecover)
|
||||
templ("address", "1");
|
||||
else if (funKind == FunctionType::Kind::SHA256)
|
||||
templ("address", "2");
|
||||
else if (funKind == FunctionType::Kind::RIPEMD160)
|
||||
templ("address", "3");
|
||||
else
|
||||
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
||||
|
||||
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
|
||||
// This ensures it can catch badly formatted input from external calls.
|
||||
@@ -1453,9 +1632,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
// but all parameters of ecrecover are value types anyway.
|
||||
encodeInPlace = false;
|
||||
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
|
||||
solUnimplementedAssert(!encodeInPlace, "");
|
||||
solUnimplementedAssert(funType.padArguments(), "");
|
||||
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
||||
|
||||
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
|
||||
if (encodeInPlace)
|
||||
{
|
||||
solUnimplementedAssert(!encodeForLibraryCall, "");
|
||||
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
|
||||
}
|
||||
else
|
||||
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
||||
templ("argumentString", argumentString);
|
||||
|
||||
// Output data will replace input data, unless we have ECRecover (then, output
|
||||
|
||||
@@ -51,6 +51,9 @@ public:
|
||||
/// Generates code to initialize the given local variable.
|
||||
void initializeLocalVar(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// Calculates expression's value and returns variable where it was stored
|
||||
IRVariable evaluateExpression(Expression const& _expression, Type const& _to);
|
||||
|
||||
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||
bool visit(Conditional const& _conditional) override;
|
||||
bool visit(Assignment const& _assignment) override;
|
||||
|
||||
@@ -29,8 +29,7 @@ class Expression;
|
||||
|
||||
/**
|
||||
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
|
||||
* of a specific S
|
||||
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
|
||||
* of a specific Solidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
|
||||
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
|
||||
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
|
||||
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
|
||||
|
||||
@@ -304,7 +304,10 @@ void BMC::endVisit(UnaryOperation const& _op)
|
||||
{
|
||||
SMTEncoder::endVisit(_op);
|
||||
|
||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
||||
if (
|
||||
_op.annotation().type->category() == Type::Category::RationalNumber ||
|
||||
_op.annotation().type->category() == Type::Category::FixedPoint
|
||||
)
|
||||
return;
|
||||
|
||||
switch (_op.getOperator())
|
||||
|
||||
@@ -194,7 +194,8 @@ void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation,
|
||||
pushCallStack({_definition, _invocation});
|
||||
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
|
||||
{
|
||||
modifier->body().accept(*this);
|
||||
if (modifier->isImplemented())
|
||||
modifier->body().accept(*this);
|
||||
popCallStack();
|
||||
}
|
||||
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
|
||||
@@ -457,6 +458,9 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
||||
|
||||
createExpr(_op);
|
||||
|
||||
if (_op.annotation().type->category() == Type::Category::FixedPoint)
|
||||
return;
|
||||
|
||||
switch (_op.getOperator())
|
||||
{
|
||||
case Token::Not: // !
|
||||
|
||||
@@ -69,8 +69,14 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(isArray(_type.category()), "");
|
||||
auto arrayType = dynamic_cast<frontend::ArrayType const*>(&_type);
|
||||
frontend::ArrayType const* arrayType = nullptr;
|
||||
if (auto const* arr = dynamic_cast<frontend::ArrayType const*>(&_type))
|
||||
arrayType = arr;
|
||||
else if (auto const* slice = dynamic_cast<frontend::ArraySliceType const*>(&_type))
|
||||
arrayType = &slice->arrayType();
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(arrayType, "");
|
||||
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
|
||||
}
|
||||
@@ -297,7 +303,8 @@ bool isMapping(frontend::Type::Category _category)
|
||||
bool isArray(frontend::Type::Category _category)
|
||||
{
|
||||
return _category == frontend::Type::Category::Array ||
|
||||
_category == frontend::Type::Category::StringLiteral;
|
||||
_category == frontend::Type::Category::StringLiteral ||
|
||||
_category == frontend::Type::Category::ArraySlice;
|
||||
}
|
||||
|
||||
bool isTuple(frontend::Type::Category _category)
|
||||
|
||||
@@ -237,8 +237,8 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort)
|
||||
z3::func_decl tupleConstructor = m_context.tuple_sort(
|
||||
tupleSort.name.c_str(),
|
||||
tupleSort.members.size(),
|
||||
&cMembers[0],
|
||||
&sorts[0],
|
||||
cMembers.data(),
|
||||
sorts.data(),
|
||||
projs
|
||||
);
|
||||
return tupleConstructor.range();
|
||||
|
||||
@@ -1129,15 +1129,21 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
|
||||
map<ContractDefinition const*, string const> otherYulSources;
|
||||
|
||||
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
|
||||
if (!compiledContract.yulIR.empty())
|
||||
return;
|
||||
|
||||
string dependenciesSource;
|
||||
for (auto const* dependency: _contract.annotation().contractDependencies)
|
||||
{
|
||||
generateIR(*dependency);
|
||||
otherYulSources.emplace(dependency, m_contracts.at(dependency->fullyQualifiedName()).yulIR);
|
||||
}
|
||||
|
||||
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
|
||||
}
|
||||
|
||||
void CompilerStack::generateEwasm(ContractDefinition const& _contract)
|
||||
@@ -1265,6 +1271,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
{
|
||||
details["yulDetails"] = Json::objectValue;
|
||||
details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation;
|
||||
details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps;
|
||||
}
|
||||
|
||||
meta["settings"]["optimizer"]["details"] = std::move(details);
|
||||
|
||||
@@ -23,12 +23,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
struct OptimiserSettings
|
||||
{
|
||||
static char constexpr DefaultYulOptimiserSteps[] =
|
||||
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
|
||||
"["
|
||||
"xarrscLM" // Turn into SSA and simplify
|
||||
"cCTUtTOntnfDIul" // Perform structural simplification
|
||||
"Lcul" // Simplify again
|
||||
"Vcul jj" // Reverse SSA
|
||||
|
||||
// should have good "compilability" property here.
|
||||
|
||||
"eul" // Run functional expression inliner
|
||||
"xarulrul" // Prune a bit more in SSA
|
||||
"xarrcL" // Turn into SSA again and simplify
|
||||
"gvif" // Run full inliner
|
||||
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
|
||||
"]"
|
||||
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
|
||||
|
||||
/// No optimisations at all - not recommended.
|
||||
static OptimiserSettings none()
|
||||
{
|
||||
@@ -74,6 +93,7 @@ struct OptimiserSettings
|
||||
runConstantOptimiser == _other.runConstantOptimiser &&
|
||||
optimizeStackAllocation == _other.optimizeStackAllocation &&
|
||||
runYulOptimiser == _other.runYulOptimiser &&
|
||||
yulOptimiserSteps == _other.yulOptimiserSteps &&
|
||||
expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment;
|
||||
}
|
||||
|
||||
@@ -95,6 +115,11 @@ struct OptimiserSettings
|
||||
bool optimizeStackAllocation = false;
|
||||
/// Yul optimiser with default settings. Will only run on certain parts of the code for now.
|
||||
bool runYulOptimiser = false;
|
||||
/// Sequence of optimisation steps to be performed by Yul optimiser.
|
||||
/// Note that there are some hard-coded steps in the optimiser and you cannot disable
|
||||
/// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want
|
||||
/// no optimisations.
|
||||
std::string yulOptimiserSteps = DefaultYulOptimiserSteps;
|
||||
/// This specifies an estimate on how often each opcode in this assembly will be executed,
|
||||
/// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
|
||||
size_t expectedExecutionsPerDeployment = 200;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <libsolidity/ast/ASTJsonConverter.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolutil/JSON.h>
|
||||
@@ -402,6 +403,33 @@ std::optional<Json::Value> checkOptimizerDetail(Json::Value const& _details, std
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting)
|
||||
{
|
||||
if (_details.isMember(_name))
|
||||
{
|
||||
if (_details[_name].isString())
|
||||
{
|
||||
try
|
||||
{
|
||||
yul::OptimiserSuite::validateSequence(_details[_name].asString());
|
||||
}
|
||||
catch (yul::OptimizerException const& _exception)
|
||||
{
|
||||
return formatFatalError(
|
||||
"JSONError",
|
||||
"Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what()
|
||||
);
|
||||
}
|
||||
|
||||
_setting = _details[_name].asString();
|
||||
}
|
||||
else
|
||||
return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string");
|
||||
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Json::Value> checkMetadataKeys(Json::Value const& _input)
|
||||
{
|
||||
if (_input.isObject())
|
||||
@@ -511,10 +539,12 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
|
||||
if (!settings.runYulOptimiser)
|
||||
return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled.");
|
||||
|
||||
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails"))
|
||||
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails"))
|
||||
return *result;
|
||||
if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation))
|
||||
return *error;
|
||||
if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps))
|
||||
return *error;
|
||||
}
|
||||
}
|
||||
return { std::move(settings) };
|
||||
|
||||
@@ -595,13 +595,13 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
|
||||
|
||||
ASTPointer<Block> block;
|
||||
nodeFactory.markEndPosition();
|
||||
if (m_scanner->currentToken() != Token::Semicolon)
|
||||
if (m_scanner->currentToken() == Token::Semicolon)
|
||||
m_scanner->next();
|
||||
else
|
||||
{
|
||||
block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
}
|
||||
else
|
||||
m_scanner->next(); // just consume the ';'
|
||||
return nodeFactory.createNode<FunctionDefinition>(
|
||||
name,
|
||||
header.visibility,
|
||||
@@ -851,9 +851,16 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
|
||||
break;
|
||||
}
|
||||
|
||||
ASTPointer<Block> block;
|
||||
nodeFactory.markEndPosition();
|
||||
if (m_scanner->currentToken() != Token::Semicolon)
|
||||
{
|
||||
block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
}
|
||||
else
|
||||
m_scanner->next(); // just consume the ';'
|
||||
|
||||
ASTPointer<Block> block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
return nodeFactory.createNode<ModifierDefinition>(name, documentation, parameters, isVirtual, overrides, block);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user