This commit is contained in:
Daniel Kirchner 2023-06-20 04:08:23 +02:00
parent 4357b0316b
commit bd0e0fcdbe
15 changed files with 586 additions and 156 deletions

View File

@ -268,8 +268,10 @@ namespace solidity::langutil
/* Yul-specific tokens, but not keywords. */ \ /* Yul-specific tokens, but not keywords. */ \
T(Leave, "leave", 0) \ T(Leave, "leave", 0) \
\ \
T(NonExperimentalEnd, nullptr, 0) /* used as non-experimental enum end marker */ \
/* Experimental Solidity specific keywords. */ \ /* Experimental Solidity specific keywords. */ \
K(Word, "word", 0) \ K(Word, "word", 0) \
K(Void, "void", 0) \
K(StaticAssert, "static_assert", 0) \ K(StaticAssert, "static_assert", 0) \
T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \ T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \
\ \
@ -295,7 +297,10 @@ namespace TokenTraits
constexpr size_t count() { return static_cast<size_t>(Token::NUM_TOKENS); } constexpr size_t count() { return static_cast<size_t>(Token::NUM_TOKENS); }
// Predicates // Predicates
constexpr bool isElementaryTypeName(Token tok) { return (Token::Int <= tok && tok < Token::TypesEnd) || tok == Token::Word; } constexpr bool isElementaryTypeName(Token tok)
{
return (Token::Int <= tok && tok < Token::TypesEnd) || tok == Token::Word || tok == Token::Void;
}
constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; } constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; }
constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; } constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; }
constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd || constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd ||
@ -331,11 +336,11 @@ namespace TokenTraits
{ {
return tok == Token::Assembly || tok == Token::Contract || tok == Token::External || tok == Token::Fallback || return tok == Token::Assembly || tok == Token::Contract || tok == Token::External || tok == Token::Fallback ||
tok == Token::Pragma || tok == Token::Import || tok == Token::As || tok == Token::Function || tok == Token::Let || tok == Token::Pragma || tok == Token::Import || tok == Token::As || tok == Token::Function || tok == Token::Let ||
(tok >= Token::Word && tok < Token::ExperimentalEnd); (tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd);
} }
constexpr bool isExperimentalSolidityOnlyKeyword(Token tok) constexpr bool isExperimentalSolidityOnlyKeyword(Token tok)
{ {
return tok >= Token::Word && tok < Token::ExperimentalEnd; return tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd;
} }
bool isYulKeyword(std::string const& _literal); bool isYulKeyword(std::string const& _literal);

View File

@ -98,6 +98,7 @@ set(sources
codegen/ReturnInfo.cpp codegen/ReturnInfo.cpp
codegen/YulUtilFunctions.h codegen/YulUtilFunctions.h
codegen/YulUtilFunctions.cpp codegen/YulUtilFunctions.cpp
codegen/experimental/IRGenerationContext.h
codegen/experimental/IRGenerator.cpp codegen/experimental/IRGenerator.cpp
codegen/experimental/IRGenerator.h codegen/experimental/IRGenerator.h
codegen/experimental/IRGeneratorForStatements.cpp codegen/experimental/IRGeneratorForStatements.cpp

View File

@ -36,7 +36,7 @@ bool Analysis::check(vector<shared_ptr<SourceUnit const>> const& _sourceUnits)
for (auto source: _sourceUnits) for (auto source: _sourceUnits)
if (!syntaxRestrictor.check(*source)) if (!syntaxRestrictor.check(*source))
return false; return false;
TypeInference typeInference{m_errorReporter}; TypeInference typeInference{*this};
for (auto source: _sourceUnits) for (auto source: _sourceUnits)
if (!typeInference.analyze(*source)) if (!typeInference.analyze(*source))
return false; return false;

View File

@ -38,7 +38,11 @@ class Analysis
{ {
public: public:
Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId); Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId);
Analysis(Analysis const&) = delete;
Analysis const& operator=(Analysis const&) = delete;
bool check(std::vector<std::shared_ptr<SourceUnit const>> const& _sourceUnits); bool check(std::vector<std::shared_ptr<SourceUnit const>> const& _sourceUnits);
langutil::ErrorReporter& errorReporter() { return m_errorReporter; }
uint64_t maxAstId() const { return m_maxAstId; }
private: private:
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;
uint64_t m_maxAstId = 0; uint64_t m_maxAstId = 0;

View File

@ -43,6 +43,7 @@ private:
bool visit(ContractDefinition const& _contractDefinition) override; bool visit(ContractDefinition const& _contractDefinition) override;
bool visit(FunctionDefinition const& _functionDefinition) override; bool visit(FunctionDefinition const& _functionDefinition) override;
bool visit(ExpressionStatement const&) override { return true; } bool visit(ExpressionStatement const&) override { return true; }
bool visit(FunctionCall const&) override { return true; }
bool visit(Assignment const&) override { return true; } bool visit(Assignment const&) override { return true; }
bool visit(Block const&) override { return true; } bool visit(Block const&) override { return true; }
bool visit(InlineAssembly const&) override { return true; } bool visit(InlineAssembly const&) override { return true; }

View File

@ -18,17 +18,39 @@
#include <libsolidity/analysis/experimental/TypeInference.h> #include <libsolidity/analysis/experimental/TypeInference.h>
#include <libsolidity/analysis/experimental/Analysis.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysis.h>
#include <libyul/AsmAnalysisInfo.h> #include <libyul/AsmAnalysisInfo.h>
#include <libyul/AST.h> #include <libyul/AST.h>
#include <range/v3/view/transform.hpp>
using namespace std; using namespace std;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace solidity::frontend::experimental; using namespace solidity::frontend::experimental;
using namespace solidity::langutil; using namespace solidity::langutil;
TypeInference::TypeInference(Analysis& _analysis):
m_analysis(_analysis),
m_errorReporter(_analysis.errorReporter())
{
for (auto [type, name, arity]: std::initializer_list<std::tuple<BuiltinType, const char*, uint64_t>> {
{BuiltinType::Void, "void", 0},
{BuiltinType::Function, "fun", 2},
{BuiltinType::Unit, "unit", 0},
{BuiltinType::Pair, "pair", 2},
{BuiltinType::Word, "word", 0}
})
m_typeSystem.declareBuiltinType(type, name, arity);
m_voidType = m_typeSystem.builtinType(BuiltinType::Void, {});
m_wordType = m_typeSystem.builtinType(BuiltinType::Word, {});
m_env = make_unique<TypeEnvironment>(m_typeSystem);
m_typeAnnotations.resize(_analysis.maxAstId());
}
bool TypeInference::analyze(SourceUnit const& _sourceUnit) bool TypeInference::analyze(SourceUnit const& _sourceUnit)
{ {
_sourceUnit.accept(*this); _sourceUnit.accept(*this);
@ -37,12 +59,36 @@ bool TypeInference::analyze(SourceUnit const& _sourceUnit)
bool TypeInference::visit(FunctionDefinition const& _functionDefinition) bool TypeInference::visit(FunctionDefinition const& _functionDefinition)
{ {
ScopedSaveAndRestore env{m_env, {}}; auto& functionAnnotation = annotation(_functionDefinition);
_functionDefinition.parameterList().accept(*this); if (functionAnnotation.type)
if (_functionDefinition.returnParameterList()) return false;
_functionDefinition.returnParameterList()->accept(*this);
_functionDefinition.body().accept(*this); Type functionType;
{
_functionDefinition.parameterList().accept(*this);
if (_functionDefinition.returnParameterList())
_functionDefinition.returnParameterList()->accept(*this);
_functionDefinition.body().accept(*this);
auto typeFromParameterList = [&](ParameterList const* _list) {
if (!_list)
return m_typeSystem.builtinType(BuiltinType::Unit, {});
return TypeSystemHelpers{m_typeSystem}.tupleType(_list->parameters() | ranges::view::transform([&](auto _param) {
auto& argAnnotation = annotation(*_param);
solAssert(argAnnotation.type);
return *argAnnotation.type;
}) | ranges::to<std::vector<Type>>);
};
Type argType = typeFromParameterList(&_functionDefinition.parameterList());
Type resultType = typeFromParameterList(_functionDefinition.returnParameterList().get());
functionType = m_typeSystem.builtinType(BuiltinType::Function, {argType, resultType});
}
functionAnnotation.type = functionType;
m_errorReporter.warning(0000_error, _functionDefinition.location(), m_typeSystem.typeToString(m_typeSystem.resolve(functionType)));
return false; return false;
} }
@ -52,6 +98,20 @@ bool TypeInference::visit(ParameterList const&)
return true; return true;
} }
void TypeInference::endVisit(ParameterList const& _parameterList)
{
auto& listAnnotation = annotation(_parameterList);
solAssert(!listAnnotation.type);
std::vector<Type> argTypes;
for(auto arg: _parameterList.parameters())
{
auto& argAnnotation = annotation(*arg);
solAssert(argAnnotation.type);
argTypes.emplace_back(*argAnnotation.type);
}
listAnnotation.type = TypeSystemHelpers{m_typeSystem}.tupleType(argTypes);
}
bool TypeInference::visitNode(ASTNode const& _node) bool TypeInference::visitNode(ASTNode const& _node)
{ {
m_errorReporter.typeError(0000_error, _node.location(), "Unsupported AST node during type inference."); m_errorReporter.typeError(0000_error, _node.location(), "Unsupported AST node during type inference.");
@ -65,7 +125,9 @@ experimental::Type TypeInference::fromTypeName(TypeName const& _typeName)
switch(elementaryTypeName->typeName().token()) switch(elementaryTypeName->typeName().token())
{ {
case Token::Word: case Token::Word:
return WordType{}; return m_wordType;
case Token::Void:
return m_voidType;
default: default:
m_errorReporter.typeError(0000_error, _typeName.location(), "Only elementary types are supported."); m_errorReporter.typeError(0000_error, _typeName.location(), "Only elementary types are supported.");
break; break;
@ -73,7 +135,8 @@ experimental::Type TypeInference::fromTypeName(TypeName const& _typeName)
} }
else else
m_errorReporter.typeError(0000_error, _typeName.location(), "Only elementary types are supported."); m_errorReporter.typeError(0000_error, _typeName.location(), "Only elementary types are supported.");
return m_env.freshFreeType(); // TODO: free type?
return m_typeSystem.freshTypeVariable();
} }
@ -101,7 +164,9 @@ bool TypeInference::visit(InlineAssembly const& _inlineAssembly)
Declaration const* declaration = identifierInfo.declaration; Declaration const* declaration = identifierInfo.declaration;
solAssert(!!declaration, ""); solAssert(!!declaration, "");
m_env.assignType(m_typeSystem, declaration, WordType{}); auto& declarationAnnotation = annotation(*declaration);
solAssert(declarationAnnotation.type);
m_typeSystem.unify(*declarationAnnotation.type, m_wordType);
identifierInfo.valueSize = 1; identifierInfo.valueSize = 1;
return true; return true;
}; };
@ -120,19 +185,83 @@ bool TypeInference::visit(InlineAssembly const& _inlineAssembly)
bool TypeInference::visit(VariableDeclaration const& _variableDeclaration) bool TypeInference::visit(VariableDeclaration const& _variableDeclaration)
{ {
Type type = _variableDeclaration.hasTypeName() ? fromTypeName(_variableDeclaration.typeName()) : m_typeSystem.freshTypeVariable(); solAssert(!_variableDeclaration.value());
m_env.assignType(m_typeSystem, &_variableDeclaration, type); auto& variableAnnotation = annotation(_variableDeclaration);
solAssert(!variableAnnotation.type);
variableAnnotation.type = [&] {
if (_variableDeclaration.hasTypeName())
return fromTypeName(_variableDeclaration.typeName());
else
return m_typeSystem.freshTypeVariable();
}();
return false; return false;
} }
bool TypeInference::visit(Assignment const& _assignment) bool TypeInference::visit(Assignment const&)
{ {
(void)_assignment;
return true; return true;
} }
void TypeInference::endVisit(Assignment const& _assignment)
{
auto& assignmentAnnotation = annotation(_assignment);
solAssert(!assignmentAnnotation.type);
auto& lhsAnnotation = annotation(_assignment.leftHandSide());
solAssert(lhsAnnotation.type);
auto& rhsAnnotation = annotation(_assignment.rightHandSide());
solAssert(rhsAnnotation.type);
m_typeSystem.unify(*lhsAnnotation.type, *rhsAnnotation.type);
assignmentAnnotation.type = m_typeSystem.resolve(*lhsAnnotation.type);
}
TypeInference::TypeAnnotation& TypeInference::annotation(ASTNode const& _node)
{
auto& annotation = m_typeAnnotations.at(static_cast<size_t>(_node.id()));
if (!annotation)
annotation = make_unique<TypeAnnotation>();
return *annotation;
}
bool TypeInference::visit(Identifier const& _identifier) bool TypeInference::visit(Identifier const& _identifier)
{ {
(void)_identifier; auto& identifierAnnotation = annotation(_identifier);
solAssert(!identifierAnnotation.type);
auto const* referencedDeclaration = _identifier.annotation().referencedDeclaration;
solAssert(referencedDeclaration);
auto& declarationAnnotation = annotation(*referencedDeclaration);
if (!declarationAnnotation.type)
referencedDeclaration->accept(*this);
solAssert(declarationAnnotation.type);
identifierAnnotation.type = declarationAnnotation.type;
return true; return true;
} }
bool TypeInference::visit(FunctionCall const&) { return true; }
void TypeInference::endVisit(FunctionCall const& _functionCall)
{
auto& functionCallAnnotation = annotation(_functionCall);
solAssert(!functionCallAnnotation.type);
auto& expressionAnnotation = annotation(_functionCall.expression());
solAssert(expressionAnnotation.type);
Type functionType = m_typeSystem.fresh(*expressionAnnotation.type);
std::vector<Type> argTypes;
for(auto arg: _functionCall.arguments())
{
auto& argAnnotation = annotation(*arg);
solAssert(argAnnotation.type);
argTypes.emplace_back(*argAnnotation.type);
}
Type argTuple = TypeSystemHelpers{m_typeSystem}.tupleType(argTypes);
Type genericFunctionType = TypeSystemHelpers{m_typeSystem}.functionType(argTuple, m_typeSystem.freshTypeVariable());
m_typeSystem.unify(genericFunctionType, functionType);
functionCallAnnotation.type = m_typeSystem.resolve(std::get<1>(TypeSystemHelpers{m_typeSystem}.destFunctionType(m_typeSystem.resolve(genericFunctionType))));
}

View File

@ -27,10 +27,12 @@
namespace solidity::frontend::experimental namespace solidity::frontend::experimental
{ {
class Analysis;
class TypeInference: public ASTConstVisitor class TypeInference: public ASTConstVisitor
{ {
public: public:
TypeInference(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} TypeInference(Analysis& _analysis);
bool analyze(SourceUnit const& _sourceUnit); bool analyze(SourceUnit const& _sourceUnit);
private: private:
@ -40,6 +42,7 @@ private:
bool visit(FunctionDefinition const& _functionDefinition) override; bool visit(FunctionDefinition const& _functionDefinition) override;
bool visit(ParameterList const& _parameterList) override; bool visit(ParameterList const& _parameterList) override;
void endVisit(ParameterList const& _parameterList) override;
bool visit(SourceUnit const&) override { return true; } bool visit(SourceUnit const&) override { return true; }
bool visit(ContractDefinition const&) override { return true; } bool visit(ContractDefinition const&) override { return true; }
bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(InlineAssembly const& _inlineAssembly) override;
@ -47,14 +50,29 @@ private:
bool visit(ExpressionStatement const&) override { return true; } bool visit(ExpressionStatement const&) override { return true; }
bool visit(Assignment const&) override; bool visit(Assignment const&) override;
void endVisit(Assignment const&) override;
bool visit(Identifier const&) override; bool visit(Identifier const&) override;
bool visit(FunctionCall const& _functionCall) override;
void endVisit(FunctionCall const& _functionCall) override;
bool visitNode(ASTNode const& _node) override; bool visitNode(ASTNode const& _node) override;
Type fromTypeName(TypeName const& _typeName); Type fromTypeName(TypeName const& _typeName);
TypeSystem m_typeSystem; Analysis& m_analysis;
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;
TypeEnvironment m_env; TypeSystem m_typeSystem;
std::unique_ptr<TypeEnvironment> m_env;
Type m_voidType;
Type m_wordType;
struct TypeAnnotation
{
std::optional<Type> type;
};
TypeAnnotation& annotation(ASTNode const& _node);
std::vector<std::unique_ptr<TypeAnnotation>> m_typeAnnotations;
}; };
} }

View File

@ -18,47 +18,225 @@
#include <libsolidity/ast/experimental/TypeSystem.h> #include <libsolidity/ast/experimental/TypeSystem.h>
#include <libsolidity/ast/AST.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <libsolutil/Visitor.h>
#include <range/v3/to_container.hpp>
#include <range/v3/view/drop_exactly.hpp>
#include <range/v3/view/drop_last.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/zip.hpp>
#include <fmt/format.h>
using namespace std; using namespace std;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace solidity::frontend::experimental; using namespace solidity::frontend::experimental;
void TypeEnvironment::assignType(TypeSystem& _typeSystem, Declaration const* _declaration, Type _typeAssignment) std::string TypeSystem::typeToString(Type const& _type) const
{
return std::visit(util::GenericVisitor{
[&](AtomicType const& _type) {
std::stringstream stream;
if (!_type.arguments.empty())
{
stream << "(";
for (auto type: _type.arguments | ranges::views::drop_last(1))
stream << typeToString(type) << ", ";
stream << typeToString(_type.arguments.back());
stream << ") ";
}
stream << std::visit(util::GenericVisitor{
[&](Declaration const* _declaration) {
return _declaration->name();
},
[&](BuiltinType _builtinType) -> string {
return builtinTypeName(_builtinType);
}
}, _type.constructor);
return stream.str();
},
[](FreeTypeVariable const& _type) {
return fmt::format("free[{}]", _type.index());
},
[](GenericTypeVariable const& _type) {
return fmt::format("var[{}]", _type.index());
},
}, resolve(_type));
}
void TypeEnvironment::assignType(Declaration const* _declaration, Type _typeAssignment)
{ {
auto&& [type, newlyInserted] = m_types.emplace(std::piecewise_construct, std::forward_as_tuple(_declaration), std::forward_as_tuple(std::move(_typeAssignment))); auto&& [type, newlyInserted] = m_types.emplace(std::piecewise_construct, std::forward_as_tuple(_declaration), std::forward_as_tuple(std::move(_typeAssignment)));
if (!newlyInserted) if (!newlyInserted)
{ {
unify(_typeSystem, type->second, _typeAssignment); m_parent.unify(type->second, _typeAssignment);
} }
} }
Type TypeEnvironment::lookup(TypeSystem& _typeSystem, Declaration const* _declaration) std::optional<experimental::Type> TypeEnvironment::lookup(Declaration const* _declaration)
{ {
if (m_types.count(_declaration)) if (m_types.count(_declaration))
return m_types[_declaration]; return m_types[_declaration];
Type result = _typeSystem.freshTypeVariable(); return std::nullopt;
m_types.emplace(std::piecewise_construct, std::forward_as_tuple(_declaration), std::forward_as_tuple(result)); }
void TypeSystem::unify(Type _a, Type _b)
{
auto unificationFailure = [&]() {
solAssert(false, fmt::format("cannot unify {} and {}", typeToString(_a), typeToString(_b)));
};
_a = resolve(_a);
_b = resolve(_b);
std::visit(util::GenericVisitor{
[&](GenericTypeVariable _left, GenericTypeVariable _right) {
validate(_left);
validate(_right);
if (_left.index() != _right.index())
instantiate(_left, _right);
},
[&](GenericTypeVariable _var, auto) {
instantiate(_var, _b);
},
[&](auto, GenericTypeVariable _var) {
instantiate(_var, _a);
},
[&](AtomicType _atomicLeft, AtomicType _atomicRight) {
if(_atomicLeft.constructor != _atomicRight.constructor)
unificationFailure();
if (_atomicLeft.arguments.size() != _atomicRight.arguments.size())
unificationFailure();
for (auto&& [left, right]: ranges::zip_view(_atomicLeft.arguments, _atomicRight.arguments))
unify(left, right);
},
[&](auto, auto) {
unificationFailure();
}
}, _a, _b);
}
unique_ptr<TypeEnvironment> TypeEnvironment::fresh() const
{
auto newEnv = make_unique<TypeEnvironment>(m_parent);
for(auto [decl,type]: m_types)
newEnv->m_types.emplace(decl, type);
return newEnv;
}
experimental::Type TypeSystem::freshTypeVariable()
{
uint64_t index = m_typeVariables.size();
m_typeVariables.emplace_back(std::nullopt);
return GenericTypeVariable(*this, index);
}
experimental::Type TypeSystem::freshFreeType()
{
uint64_t index = m_freeTypes.size();
m_freeTypes.emplace_back(std::nullopt);
return FreeTypeVariable(*this, index);
}
void TypeSystem::instantiate(GenericTypeVariable _variable, Type _type)
{
validate(_variable);
solAssert(!m_typeVariables.at(_variable.index()).has_value());
solAssert(_variable.m_parent == this);
m_typeVariables[_variable.index()] = _type;
}
experimental::Type TypeSystem::resolve(Type _type) const
{
Type result = _type;
while(auto const* var = std::get_if<GenericTypeVariable>(&result))
if (auto value = m_typeVariables.at(var->index()))
result = *value;
else
break;
return result; return result;
} }
Type TypeEnvironment::freshFreeType()
void TypeSystem::declareBuiltinType(BuiltinType _builtinType, std::string _name, uint64_t _arity)
{ {
return FreeType{m_numFreeTypes++}; solAssert(m_builtinTypes.count(_builtinType) == 0, "Builtin type already declared.");
m_builtinTypes[_builtinType] = TypeConstructorInfo{
_name,
_arity
};
}
experimental::Type TypeSystem::builtinType(BuiltinType _builtinType, std::vector<Type> _arguments) const
{
auto const& info = m_builtinTypes.at(_builtinType);
solAssert(info.arity == _arguments.size(), "Invalid arity.");
return AtomicType{_builtinType, _arguments};
}
void TypeSystem::validate(GenericTypeVariable _variable) const
{
solAssert(_variable.m_parent == this);
solAssert(_variable.index() < m_typeVariables.size());
}
experimental::Type TypeSystemHelpers::tupleType(vector<Type> _elements) const
{
if (_elements.empty())
return typeSystem.builtinType(BuiltinType::Unit, {});
if (_elements.size() == 1)
return _elements.front();
Type result = _elements.back();
for (Type type: _elements | ranges::view::reverse | ranges::view::drop_exactly(1))
result = typeSystem.builtinType(BuiltinType::Pair, {type, result});
return result;
}
experimental::Type TypeSystemHelpers::functionType(experimental::Type _argType, experimental::Type _resultType) const
{
return typeSystem.builtinType(BuiltinType::Function, {_argType, _resultType});
}
experimental::Type TypeSystem::fresh(Type _type)
{
return std::visit(util::GenericVisitor{
[&](AtomicType const& _type) -> Type {
return AtomicType{
_type.constructor,
_type.arguments | ranges::view::transform([&](Type _argType) {
return fresh(_argType);
}) | ranges::to<vector<Type>>
};
},
[&](FreeTypeVariable const&) {
return _type;
},
[&](GenericTypeVariable const&) {
return freshTypeVariable();
},
}, resolve(_type));
}
tuple<AtomicType::Constructor, vector<experimental::Type>> TypeSystemHelpers::destAtomicType(Type _functionType) const
{
using ResultType = tuple<AtomicType::Constructor, vector<Type>>;
return std::visit(util::GenericVisitor{
[&](AtomicType const& _type) -> ResultType {
return std::make_tuple(_type.constructor, _type.arguments);
},
[](auto) -> ResultType {
solAssert(false);
}
}, _functionType);
} }
void TypeEnvironment::unify(TypeSystem& _context, Type _a, Type _b) tuple<experimental::Type, experimental::Type> TypeSystemHelpers::destFunctionType(Type _functionType) const
{ {
_a = _context.resolve(_a); auto [constructor, arguments] = destAtomicType(_functionType);
_b = _context.resolve(_b); auto const* builtinType = get_if<BuiltinType>(&constructor);
if (auto* varA = get_if<TypeVariable>(&_a)) solAssert(builtinType && *builtinType == BuiltinType::Function);
_context.instantiate(*varA, _b); solAssert(arguments.size() == 2);
else if (holds_alternative<WordType>(_a)) return make_tuple(arguments.front(), arguments.back());
{ }
if (holds_alternative<WordType>(_b))
return;
else
solAssert(false, "unification failed");
}
solAssert(false, fmt::format("cannot unify {} and {}", typeToString(_a), typeToString(_b)));
}

View File

@ -19,8 +19,6 @@
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <fmt/format.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <variant> #include <variant>
@ -35,99 +33,64 @@ namespace solidity::frontend::experimental
{ {
class TypeSystem; class TypeSystem;
class TypeEnvironment;
struct SumType; struct AtomicType;
struct TupleType; struct GenericTypeVariable;
struct FunctionType; struct FreeTypeVariable;
struct WordType;
struct UserDefinedType;
struct TypeVariable;
struct FreeType;
using Type = std::variant<SumType, TupleType, FunctionType, WordType, UserDefinedType, TypeVariable, FreeType>; using Type = std::variant<AtomicType, GenericTypeVariable, FreeTypeVariable>;
struct SumType enum class BuiltinType
{ {
std::vector<Type const*> alternatives; Void,
std::string toString() const Function,
{ Unit,
return "sum"; Pair,
} Word
}; };
struct TupleType struct AtomicType
{ {
std::vector<Type const*> components; using Constructor = std::variant<BuiltinType, Declaration const*>;
std::string toString() const Constructor constructor;
{ std::vector<Type> arguments;
return "tuple";
}
}; };
struct FunctionType struct FreeTypeVariable
{ {
Type const* codomain = nullptr; TypeSystem const& parent() const { return *m_parent; }
Type const* domain = nullptr;
std::string toString() const
{
return "fun";
}
};
struct WordType
{
std::string toString() const
{
return "word";
}
};
struct UserDefinedType
{
Declaration const* declaration = nullptr;
std::vector<Type const*> arguments;
std::string toString() const
{
return "user_defined_type";
}
};
struct TypeVariable
{
std::string toString() const
{
return fmt::format("var<{}>", m_index);
}
private:
uint64_t index() const { return m_index; } uint64_t index() const { return m_index; }
private:
friend class TypeSystem; friend class TypeSystem;
TypeSystem const* m_parent = nullptr;
uint64_t m_index = 0; uint64_t m_index = 0;
TypeVariable(uint64_t _index): m_index(_index) {} FreeTypeVariable(TypeSystem const& _parent, uint64_t _index): m_parent(&_parent), m_index(_index) {}
}; };
struct FreeType struct GenericTypeVariable
{ {
uint64_t index = 0; TypeSystem const& parent() const { return *m_parent; }
std::string toString() const uint64_t index() const { return m_index; }
{ private:
return fmt::format("free<{}>", index); friend class TypeSystem;
} TypeSystem const* m_parent = nullptr;
uint64_t m_index = 0;
GenericTypeVariable(TypeSystem const& _parent, uint64_t _index): m_parent(&_parent), m_index(_index) {}
}; };
inline std::string typeToString(Type const& _type)
{
return std::visit([](auto _type) { return _type.toString(); }, _type);
}
class TypeEnvironment class TypeEnvironment
{ {
public: public:
void assignType(TypeSystem& _typeSystem, Declaration const* _declaration, Type _typeAssignment); TypeEnvironment(TypeSystem& _parent): m_parent(_parent) {}
Type lookup(TypeSystem& _typeSystem, Declaration const* _declaration); TypeEnvironment(TypeEnvironment const& _env) = delete;
Type freshFreeType(); TypeEnvironment& operator=(TypeEnvironment const& _env) = delete;
void unify(TypeSystem& _typeSystem, Type _a, Type _b); std::unique_ptr<TypeEnvironment> fresh() const;
void assignType(Declaration const* _declaration, Type _typeAssignment);
std::optional<Type> lookup(Declaration const* _declaration);
private: private:
uint64_t m_numFreeTypes = 0; TypeSystem& m_parent;
std::map<Declaration const*, Type> m_types; std::map<Declaration const*, Type> m_types;
}; };
@ -138,30 +101,38 @@ public:
TypeSystem() {} TypeSystem() {}
TypeSystem(TypeSystem const&) = delete; TypeSystem(TypeSystem const&) = delete;
TypeSystem const& operator=(TypeSystem const&) = delete; TypeSystem const& operator=(TypeSystem const&) = delete;
Type freshTypeVariable() void declareBuiltinType(BuiltinType _builtinType, std::string _name, uint64_t _arity);
Type builtinType(BuiltinType _builtinType, std::vector<Type> _arguments) const;
std::string builtinTypeName(BuiltinType _builtinType) const
{ {
uint64_t index = m_typeVariables.size(); return m_builtinTypes.at(_builtinType).name;
m_typeVariables.emplace_back(std::nullopt);
return TypeVariable(index);
}
void instantiate(TypeVariable _variable, Type _type)
{
solAssert(_variable.index() < m_typeVariables.size());
solAssert(!m_typeVariables.at(_variable.index()).has_value());
m_typeVariables[_variable.index()] = _type;
}
Type resolve(Type _type)
{
Type result = _type;
while(auto const* var = std::get_if<TypeVariable>(&result))
if (auto value = m_typeVariables.at(var->index()))
result = *value;
else
break;
return result;
} }
Type freshFreeType();
void instantiate(GenericTypeVariable _variable, Type _type);
void validate(GenericTypeVariable _variable) const;
Type resolve(Type _type) const;
std::string typeToString(Type const& _type) const;
Type freshTypeVariable();
Type fresh(Type _type);
void unify(Type _a, Type _b);
private: private:
std::vector<std::optional<Type>> m_freeTypes;
struct TypeConstructorInfo
{
std::string name;
uint64_t arity = 0;
};
std::map<BuiltinType, TypeConstructorInfo> m_builtinTypes;
std::vector<std::optional<Type>> m_typeVariables; std::vector<std::optional<Type>> m_typeVariables;
}; };
struct TypeSystemHelpers
{
TypeSystem& typeSystem;
Type tupleType(std::vector<Type> _elements) const;
Type functionType(Type _argType, Type _resultType) const;
std::tuple<AtomicType::Constructor, std::vector<Type>> destAtomicType(Type _functionType) const;
std::tuple<Type, Type> destFunctionType(Type _functionType) const;
};
} }

View File

@ -0,0 +1,43 @@
/*
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/ast/ASTForward.h>
#include <list>
#include <set>
namespace solidity::frontend::experimental
{
class Analysis;
struct IRGenerationContext
{
Analysis const& analysis;
std::list<FunctionDefinition const*> functionQueue;
std::set<FunctionDefinition const*> generatedFunctions;
void enqueueFunctionDefinition(FunctionDefinition const* _functionDefinition)
{
if (!generatedFunctions.count(_functionDefinition))
functionQueue.emplace_back(_functionDefinition);
}
};
}

View File

@ -18,6 +18,7 @@
#include <libsolidity/codegen/experimental/IRGenerator.h> #include <libsolidity/codegen/experimental/IRGenerator.h>
#include <libsolidity/codegen/experimental/IRGenerationContext.h>
#include <libsolidity/codegen/experimental/IRGeneratorForStatements.h> #include <libsolidity/codegen/experimental/IRGeneratorForStatements.h>
#include <libsolidity/codegen/ir/Common.h> #include <libsolidity/codegen/ir/Common.h>
@ -31,6 +32,8 @@
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <range/v3/view/drop_last.hpp>
#include <variant> #include <variant>
using namespace std; using namespace std;
@ -43,7 +46,7 @@ string IRGenerator::run(
ContractDefinition const& _contract, ContractDefinition const& _contract,
bytes const& /*_cborMetadata*/, bytes const& /*_cborMetadata*/,
map<ContractDefinition const*, string_view const> const& /*_otherYulSources*/ map<ContractDefinition const*, string_view const> const& /*_otherYulSources*/
) const )
{ {
Whiskers t(R"( Whiskers t(R"(
@ -66,30 +69,52 @@ string IRGenerator::run(
return t.render(); return t.render();
} }
string IRGenerator::generate(ContractDefinition const& _contract) const string IRGenerator::generate(ContractDefinition const& _contract)
{ {
std::stringstream code; std::stringstream code;
code << "{\n"; code << "{\n";
if (_contract.fallbackFunction()) if (_contract.fallbackFunction())
{ {
code << IRNames::function(*_contract.fallbackFunction()) << "()\n"; code << IRNames::function(*_contract.fallbackFunction()) << "()\n";
m_context.functionQueue.emplace_front(_contract.fallbackFunction());
} }
code << "revert(0,0)\n"; code << "revert(0,0)\n";
code << "}\n"; code << "}\n";
for (FunctionDefinition const* f: _contract.definedFunctions()) while (!m_context.functionQueue.empty())
code << generate(*f); {
FunctionDefinition const* function = m_context.functionQueue.front();
m_context.functionQueue.pop_front();
m_context.generatedFunctions.insert(function);
code << generate(*function);
}
return code.str(); return code.str();
} }
string IRGenerator::generate(FunctionDefinition const& _function) const string IRGenerator::generate(FunctionDefinition const& _function)
{ {
std::stringstream code; std::stringstream code;
code << "function " << IRNames::function(_function) << "() {\n"; code << "function " << IRNames::function(_function) << "(";
if (_function.parameters().size() > 1)
for (auto const& arg: _function.parameters() | ranges::view::drop_last(1))
code << IRNames::localVariable(*arg) << ", ";
if (!_function.parameters().empty())
code << IRNames::localVariable(*_function.parameters().back());
code << ")";
if (_function.returnParameterList() && !_function.returnParameters().empty())
{
code << " -> ";
if (_function.returnParameters().size() > 1)
for (auto const& arg: _function.returnParameters() | ranges::view::drop_last(1))
code << IRNames::localVariable(*arg) << ", ";
if (!_function.returnParameters().empty())
code << IRNames::localVariable(*_function.returnParameters().back());
}
code << "{\n";
for (auto _statement: _function.body().statements()) for (auto _statement: _function.body().statements())
{ {
IRGeneratorForStatements statementGenerator{m_analysis}; IRGeneratorForStatements statementGenerator{m_context};
code << statementGenerator.generate(*_statement); code << statementGenerator.generate(*_statement);
} }
code << "}\n"; code << "}\n";

View File

@ -18,6 +18,7 @@
#pragma once #pragma once
#include <libsolidity/codegen/experimental/IRGenerationContext.h>
#include <libsolidity/interface/DebugSettings.h> #include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/OptimiserSettings.h> #include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/ASTForward.h>
@ -53,24 +54,24 @@ public:
m_eofVersion(_eofVersion), m_eofVersion(_eofVersion),
m_debugInfoSelection(_debugInfoSelection), m_debugInfoSelection(_debugInfoSelection),
m_soliditySourceProvider(_soliditySourceProvider), m_soliditySourceProvider(_soliditySourceProvider),
m_analysis(_analysis) m_context{_analysis, {}, {}}
{} {}
std::string run( std::string run(
ContractDefinition const& _contract, ContractDefinition const& _contract,
bytes const& _cborMetadata, bytes const& _cborMetadata,
std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources
) const; );
std::string generate(ContractDefinition const& _contract) const; std::string generate(ContractDefinition const& _contract);
std::string generate(FunctionDefinition const& _function) const; std::string generate(FunctionDefinition const& _function);
private: private:
langutil::EVMVersion const m_evmVersion; langutil::EVMVersion const m_evmVersion;
std::optional<uint8_t> const m_eofVersion; std::optional<uint8_t> const m_eofVersion;
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
langutil::DebugInfoSelection m_debugInfoSelection = {}; langutil::DebugInfoSelection m_debugInfoSelection = {};
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr;
Analysis const& m_analysis; IRGenerationContext m_context;
}; };
} }

View File

@ -25,6 +25,8 @@
#include <libsolidity/codegen/ir/Common.h> #include <libsolidity/codegen/ir/Common.h>
#include <range/v3/view/drop_last.hpp>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util; using namespace solidity::util;
@ -104,7 +106,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _assembly)
CopyTranslate bodyCopier{_assembly.dialect(), _assembly.annotation().externalReferences}; CopyTranslate bodyCopier{_assembly.dialect(), _assembly.annotation().externalReferences};
yul::Statement modified = bodyCopier(_assembly.operations()); yul::Statement modified = bodyCopier(_assembly.operations());
solAssert(holds_alternative<yul::Block>(modified)); solAssert(holds_alternative<yul::Block>(modified));
m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)); m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
return false; return false;
} }
@ -119,6 +121,55 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _variab
return false; return false;
} }
bool IRGeneratorForStatements::visit(ExpressionStatement const&)
{
return true;
}
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
{
auto const* rhsVar = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration);
solAssert(rhsVar, "Can only reference identifiers referring to variables.");
m_code << "let " << IRNames::localVariable(_identifier) << " := " << IRNames::localVariable(*rhsVar) << "\n";
return false;
}
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
{
for(auto arg: _functionCall.arguments())
arg->accept(*this);
auto const* identifier = dynamic_cast<Identifier const*>(&_functionCall.expression());
solAssert(identifier, "Complex function call expressions not supported.");
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration);
solAssert(functionDefinition, "Function call expression must refer to a function definition.");
m_context.enqueueFunctionDefinition(functionDefinition);
m_code << "let " << IRNames::localVariable(_functionCall) << " := " << IRNames::function(*functionDefinition) << "(";
auto const& arguments = _functionCall.arguments();
if (arguments.size() > 1)
for (auto arg: arguments | ranges::view::drop_last(1))
m_code << IRNames::localVariable(*arg) << ", ";
if (!arguments.empty())
m_code << IRNames::localVariable(*arguments.back());
m_code << ")\n";
return false;
}
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
_assignment.rightHandSide().accept(*this);
auto const* lhs = dynamic_cast<Identifier const*>(&_assignment.leftHandSide());
solAssert(lhs, "Can only assign to identifiers.");
auto const* lhsVar = dynamic_cast<VariableDeclaration const*>(lhs->annotation().referencedDeclaration);
solAssert(lhsVar, "Can only assign to identifiers referring to variables.");
m_code << IRNames::localVariable(*lhsVar) << " := " << IRNames::localVariable(_assignment.rightHandSide()) << "\n";
m_code << "let " << IRNames::localVariable(_assignment) << " := " << IRNames::localVariable(*lhsVar) << "\n";
return false;
}
bool IRGeneratorForStatements::visitNode(ASTNode const&) bool IRGeneratorForStatements::visitNode(ASTNode const&)
{ {
solAssert(false, "Unsupported AST node during statement code generation."); solAssert(false, "Unsupported AST node during statement code generation.");

View File

@ -18,6 +18,7 @@
#pragma once #pragma once
#include <libsolidity/codegen/experimental/IRGenerationContext.h>
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <functional> #include <functional>
@ -30,15 +31,19 @@ class Analysis;
class IRGeneratorForStatements: public ASTConstVisitor class IRGeneratorForStatements: public ASTConstVisitor
{ {
public: public:
IRGeneratorForStatements(Analysis const& _analysis): m_analysis(_analysis) {} IRGeneratorForStatements(IRGenerationContext& _context): m_context(_context) {}
std::string generate(ASTNode const& _node); std::string generate(ASTNode const& _node);
private: private:
bool visit(ExpressionStatement const& _expressionStatement) override;
bool visit(Assignment const& _assignment) override;
bool visit(Identifier const& _identifier) override;
bool visit(FunctionCall const&) override;
bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
/// Default visit will reject all AST nodes that are not explicitly supported. /// Default visit will reject all AST nodes that are not explicitly supported.
bool visitNode(ASTNode const& _node) override; bool visitNode(ASTNode const& _node) override;
Analysis const& m_analysis; IRGenerationContext& m_context;
std::stringstream m_code; std::stringstream m_code;
}; };

View File

@ -1,9 +1,7 @@
pragma experimental solidity; pragma experimental solidity;
function f(a:word) -> (b:word) { function f(a) -> (b) {
assembly { b = a;
b := a
}
} }
contract C { contract C {
@ -13,7 +11,7 @@ contract C {
assembly { assembly {
x := 0x42 x := 0x42
} }
y = x; y = f(x);
assembly { assembly {
mstore(0, y) mstore(0, y)
return(0, 32) return(0, 32)
@ -23,4 +21,4 @@ contract C {
// ==== // ====
// compileViaYul: true // compileViaYul: true
// ---- // ----
// () -> 0x42 // () -> 21