diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 475e12c7c..c786e287a 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -268,8 +268,10 @@ namespace solidity::langutil /* Yul-specific tokens, but not keywords. */ \ T(Leave, "leave", 0) \ \ + T(NonExperimentalEnd, nullptr, 0) /* used as non-experimental enum end marker */ \ /* Experimental Solidity specific keywords. */ \ K(Word, "word", 0) \ + K(Void, "void", 0) \ K(StaticAssert, "static_assert", 0) \ T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \ \ @@ -295,7 +297,10 @@ namespace TokenTraits constexpr size_t count() { return static_cast(Token::NUM_TOKENS); } // 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 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 || @@ -331,11 +336,11 @@ namespace TokenTraits { 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::Word && tok < Token::ExperimentalEnd); + (tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd); } 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); diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index a42cc5432..a16a2d0f8 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -98,6 +98,7 @@ set(sources codegen/ReturnInfo.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp + codegen/experimental/IRGenerationContext.h codegen/experimental/IRGenerator.cpp codegen/experimental/IRGenerator.h codegen/experimental/IRGeneratorForStatements.cpp diff --git a/libsolidity/analysis/experimental/Analysis.cpp b/libsolidity/analysis/experimental/Analysis.cpp index 76d6f16e9..d72d1c518 100644 --- a/libsolidity/analysis/experimental/Analysis.cpp +++ b/libsolidity/analysis/experimental/Analysis.cpp @@ -36,7 +36,7 @@ bool Analysis::check(vector> const& _sourceUnits) for (auto source: _sourceUnits) if (!syntaxRestrictor.check(*source)) return false; - TypeInference typeInference{m_errorReporter}; + TypeInference typeInference{*this}; for (auto source: _sourceUnits) if (!typeInference.analyze(*source)) return false; diff --git a/libsolidity/analysis/experimental/Analysis.h b/libsolidity/analysis/experimental/Analysis.h index 56450266b..9f5a2420c 100644 --- a/libsolidity/analysis/experimental/Analysis.h +++ b/libsolidity/analysis/experimental/Analysis.h @@ -38,7 +38,11 @@ class Analysis { public: Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId); + Analysis(Analysis const&) = delete; + Analysis const& operator=(Analysis const&) = delete; bool check(std::vector> const& _sourceUnits); + langutil::ErrorReporter& errorReporter() { return m_errorReporter; } + uint64_t maxAstId() const { return m_maxAstId; } private: langutil::ErrorReporter& m_errorReporter; uint64_t m_maxAstId = 0; diff --git a/libsolidity/analysis/experimental/SyntaxRestrictor.h b/libsolidity/analysis/experimental/SyntaxRestrictor.h index ef018f5f2..91989782f 100644 --- a/libsolidity/analysis/experimental/SyntaxRestrictor.h +++ b/libsolidity/analysis/experimental/SyntaxRestrictor.h @@ -43,6 +43,7 @@ private: bool visit(ContractDefinition const& _contractDefinition) override; bool visit(FunctionDefinition const& _functionDefinition) override; bool visit(ExpressionStatement const&) override { return true; } + bool visit(FunctionCall const&) override { return true; } bool visit(Assignment const&) override { return true; } bool visit(Block const&) override { return true; } bool visit(InlineAssembly const&) override { return true; } diff --git a/libsolidity/analysis/experimental/TypeInference.cpp b/libsolidity/analysis/experimental/TypeInference.cpp index 04f8ecae8..39d670f1e 100644 --- a/libsolidity/analysis/experimental/TypeInference.cpp +++ b/libsolidity/analysis/experimental/TypeInference.cpp @@ -18,17 +18,39 @@ #include +#include #include #include #include #include +#include + using namespace std; using namespace solidity::frontend; using namespace solidity::frontend::experimental; using namespace solidity::langutil; +TypeInference::TypeInference(Analysis& _analysis): +m_analysis(_analysis), +m_errorReporter(_analysis.errorReporter()) +{ + for (auto [type, name, arity]: std::initializer_list> { + {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(m_typeSystem); + + m_typeAnnotations.resize(_analysis.maxAstId()); +} + bool TypeInference::analyze(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); @@ -37,12 +59,36 @@ bool TypeInference::analyze(SourceUnit const& _sourceUnit) bool TypeInference::visit(FunctionDefinition const& _functionDefinition) { - ScopedSaveAndRestore env{m_env, {}}; - _functionDefinition.parameterList().accept(*this); - if (_functionDefinition.returnParameterList()) - _functionDefinition.returnParameterList()->accept(*this); + auto& functionAnnotation = annotation(_functionDefinition); + if (functionAnnotation.type) + return false; - _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>); + }; + + 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; } @@ -52,6 +98,20 @@ bool TypeInference::visit(ParameterList const&) return true; } +void TypeInference::endVisit(ParameterList const& _parameterList) +{ + auto& listAnnotation = annotation(_parameterList); + solAssert(!listAnnotation.type); + std::vector 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) { 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()) { case Token::Word: - return WordType{}; + return m_wordType; + case Token::Void: + return m_voidType; default: m_errorReporter.typeError(0000_error, _typeName.location(), "Only elementary types are supported."); break; @@ -73,7 +135,8 @@ experimental::Type TypeInference::fromTypeName(TypeName const& _typeName) } else 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; 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; return true; }; @@ -120,19 +185,83 @@ bool TypeInference::visit(InlineAssembly const& _inlineAssembly) bool TypeInference::visit(VariableDeclaration const& _variableDeclaration) { - Type type = _variableDeclaration.hasTypeName() ? fromTypeName(_variableDeclaration.typeName()) : m_typeSystem.freshTypeVariable(); - m_env.assignType(m_typeSystem, &_variableDeclaration, type); + solAssert(!_variableDeclaration.value()); + auto& variableAnnotation = annotation(_variableDeclaration); + solAssert(!variableAnnotation.type); + variableAnnotation.type = [&] { + if (_variableDeclaration.hasTypeName()) + return fromTypeName(_variableDeclaration.typeName()); + else + return m_typeSystem.freshTypeVariable(); + }(); return false; } -bool TypeInference::visit(Assignment const& _assignment) +bool TypeInference::visit(Assignment const&) { - (void)_assignment; 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(_node.id())); + if (!annotation) + annotation = make_unique(); + return *annotation; +} + 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; } + +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 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)))); +} diff --git a/libsolidity/analysis/experimental/TypeInference.h b/libsolidity/analysis/experimental/TypeInference.h index 06eab47c7..18ab717cf 100644 --- a/libsolidity/analysis/experimental/TypeInference.h +++ b/libsolidity/analysis/experimental/TypeInference.h @@ -27,10 +27,12 @@ namespace solidity::frontend::experimental { +class Analysis; + class TypeInference: public ASTConstVisitor { public: - TypeInference(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + TypeInference(Analysis& _analysis); bool analyze(SourceUnit const& _sourceUnit); private: @@ -40,6 +42,7 @@ private: bool visit(FunctionDefinition const& _functionDefinition) override; bool visit(ParameterList const& _parameterList) override; + void endVisit(ParameterList const& _parameterList) override; bool visit(SourceUnit const&) override { return true; } bool visit(ContractDefinition const&) override { return true; } bool visit(InlineAssembly const& _inlineAssembly) override; @@ -47,14 +50,29 @@ private: bool visit(ExpressionStatement const&) override { return true; } bool visit(Assignment const&) override; + void endVisit(Assignment 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; Type fromTypeName(TypeName const& _typeName); - TypeSystem m_typeSystem; + Analysis& m_analysis; langutil::ErrorReporter& m_errorReporter; - TypeEnvironment m_env; + TypeSystem m_typeSystem; + std::unique_ptr m_env; + Type m_voidType; + Type m_wordType; + + struct TypeAnnotation + { + std::optional type; + }; + + TypeAnnotation& annotation(ASTNode const& _node); + + std::vector> m_typeAnnotations; }; } \ No newline at end of file diff --git a/libsolidity/ast/experimental/TypeSystem.cpp b/libsolidity/ast/experimental/TypeSystem.cpp index 384968618..00beee261 100644 --- a/libsolidity/ast/experimental/TypeSystem.cpp +++ b/libsolidity/ast/experimental/TypeSystem.cpp @@ -18,47 +18,225 @@ #include +#include #include +#include + +#include +#include +#include +#include +#include + +#include + using namespace std; using namespace solidity::frontend; 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))); if (!newlyInserted) { - unify(_typeSystem, type->second, _typeAssignment); + m_parent.unify(type->second, _typeAssignment); } } -Type TypeEnvironment::lookup(TypeSystem& _typeSystem, Declaration const* _declaration) +std::optional TypeEnvironment::lookup(Declaration const* _declaration) { if (m_types.count(_declaration)) return m_types[_declaration]; - Type result = _typeSystem.freshTypeVariable(); - m_types.emplace(std::piecewise_construct, std::forward_as_tuple(_declaration), std::forward_as_tuple(result)); + return std::nullopt; +} + + +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::fresh() const +{ + auto newEnv = make_unique(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(&result)) + if (auto value = m_typeVariables.at(var->index())) + result = *value; + else + break; 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 _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 _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> + }; + }, + [&](FreeTypeVariable const&) { + return _type; + }, + [&](GenericTypeVariable const&) { + return freshTypeVariable(); + }, + }, resolve(_type)); +} + +tuple> TypeSystemHelpers::destAtomicType(Type _functionType) const +{ + using ResultType = tuple>; + 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 TypeSystemHelpers::destFunctionType(Type _functionType) const { - _a = _context.resolve(_a); - _b = _context.resolve(_b); - if (auto* varA = get_if(&_a)) - _context.instantiate(*varA, _b); - else if (holds_alternative(_a)) - { - if (holds_alternative(_b)) - return; - else - solAssert(false, "unification failed"); - } - - solAssert(false, fmt::format("cannot unify {} and {}", typeToString(_a), typeToString(_b))); -} \ No newline at end of file + auto [constructor, arguments] = destAtomicType(_functionType); + auto const* builtinType = get_if(&constructor); + solAssert(builtinType && *builtinType == BuiltinType::Function); + solAssert(arguments.size() == 2); + return make_tuple(arguments.front(), arguments.back()); +} diff --git a/libsolidity/ast/experimental/TypeSystem.h b/libsolidity/ast/experimental/TypeSystem.h index da1d546a6..2f5aeed13 100644 --- a/libsolidity/ast/experimental/TypeSystem.h +++ b/libsolidity/ast/experimental/TypeSystem.h @@ -19,8 +19,6 @@ #include -#include - #include #include #include @@ -35,99 +33,64 @@ namespace solidity::frontend::experimental { class TypeSystem; +class TypeEnvironment; -struct SumType; -struct TupleType; -struct FunctionType; -struct WordType; -struct UserDefinedType; -struct TypeVariable; -struct FreeType; +struct AtomicType; +struct GenericTypeVariable; +struct FreeTypeVariable; -using Type = std::variant; +using Type = std::variant; -struct SumType +enum class BuiltinType { - std::vector alternatives; - std::string toString() const - { - return "sum"; - } + Void, + Function, + Unit, + Pair, + Word }; -struct TupleType +struct AtomicType { - std::vector components; - std::string toString() const - { - return "tuple"; - } + using Constructor = std::variant; + Constructor constructor; + std::vector arguments; + }; -struct FunctionType +struct FreeTypeVariable { - Type const* codomain = nullptr; - 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 arguments; - std::string toString() const - { - return "user_defined_type"; - } -}; - -struct TypeVariable -{ - std::string toString() const - { - return fmt::format("var<{}>", m_index); - } -private: + TypeSystem const& parent() const { return *m_parent; } uint64_t index() const { return m_index; } +private: friend class TypeSystem; + TypeSystem const* m_parent = nullptr; 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; - std::string toString() const - { - return fmt::format("free<{}>", index); - } + TypeSystem const& parent() const { return *m_parent; } + uint64_t index() const { return m_index; } +private: + 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 { public: - void assignType(TypeSystem& _typeSystem, Declaration const* _declaration, Type _typeAssignment); - Type lookup(TypeSystem& _typeSystem, Declaration const* _declaration); - Type freshFreeType(); - void unify(TypeSystem& _typeSystem, Type _a, Type _b); + TypeEnvironment(TypeSystem& _parent): m_parent(_parent) {} + TypeEnvironment(TypeEnvironment const& _env) = delete; + TypeEnvironment& operator=(TypeEnvironment const& _env) = delete; + std::unique_ptr fresh() const; + void assignType(Declaration const* _declaration, Type _typeAssignment); + std::optional lookup(Declaration const* _declaration); private: - uint64_t m_numFreeTypes = 0; + TypeSystem& m_parent; std::map m_types; }; @@ -138,30 +101,38 @@ public: TypeSystem() {} TypeSystem(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 _arguments) const; + std::string builtinTypeName(BuiltinType _builtinType) const { - uint64_t index = m_typeVariables.size(); - 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(&result)) - if (auto value = m_typeVariables.at(var->index())) - result = *value; - else - break; - return result; + return m_builtinTypes.at(_builtinType).name; } + 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: + std::vector> m_freeTypes; + struct TypeConstructorInfo + { + std::string name; + uint64_t arity = 0; + }; + std::map m_builtinTypes; std::vector> m_typeVariables; }; +struct TypeSystemHelpers +{ + TypeSystem& typeSystem; + Type tupleType(std::vector _elements) const; + Type functionType(Type _argType, Type _resultType) const; + std::tuple> destAtomicType(Type _functionType) const; + std::tuple destFunctionType(Type _functionType) const; +}; + } \ No newline at end of file diff --git a/libsolidity/codegen/experimental/IRGenerationContext.h b/libsolidity/codegen/experimental/IRGenerationContext.h new file mode 100644 index 000000000..c52215e70 --- /dev/null +++ b/libsolidity/codegen/experimental/IRGenerationContext.h @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +struct IRGenerationContext +{ + Analysis const& analysis; + std::list functionQueue; + std::set generatedFunctions; + void enqueueFunctionDefinition(FunctionDefinition const* _functionDefinition) + { + if (!generatedFunctions.count(_functionDefinition)) + functionQueue.emplace_back(_functionDefinition); + } +}; + +} diff --git a/libsolidity/codegen/experimental/IRGenerator.cpp b/libsolidity/codegen/experimental/IRGenerator.cpp index 857f0c48b..978e1e9ad 100644 --- a/libsolidity/codegen/experimental/IRGenerator.cpp +++ b/libsolidity/codegen/experimental/IRGenerator.cpp @@ -18,6 +18,7 @@ #include +#include #include #include @@ -31,6 +32,8 @@ #include +#include + #include using namespace std; @@ -43,7 +46,7 @@ string IRGenerator::run( ContractDefinition const& _contract, bytes const& /*_cborMetadata*/, map const& /*_otherYulSources*/ -) const +) { Whiskers t(R"( @@ -66,30 +69,52 @@ string IRGenerator::run( return t.render(); } -string IRGenerator::generate(ContractDefinition const& _contract) const +string IRGenerator::generate(ContractDefinition const& _contract) { std::stringstream code; code << "{\n"; if (_contract.fallbackFunction()) { code << IRNames::function(*_contract.fallbackFunction()) << "()\n"; + m_context.functionQueue.emplace_front(_contract.fallbackFunction()); } code << "revert(0,0)\n"; code << "}\n"; - for (FunctionDefinition const* f: _contract.definedFunctions()) - code << generate(*f); + while (!m_context.functionQueue.empty()) + { + FunctionDefinition const* function = m_context.functionQueue.front(); + m_context.functionQueue.pop_front(); + m_context.generatedFunctions.insert(function); + code << generate(*function); + } return code.str(); } -string IRGenerator::generate(FunctionDefinition const& _function) const +string IRGenerator::generate(FunctionDefinition const& _function) { 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()) { - IRGeneratorForStatements statementGenerator{m_analysis}; + IRGeneratorForStatements statementGenerator{m_context}; code << statementGenerator.generate(*_statement); } code << "}\n"; diff --git a/libsolidity/codegen/experimental/IRGenerator.h b/libsolidity/codegen/experimental/IRGenerator.h index d7a36c57a..cbf3272b5 100644 --- a/libsolidity/codegen/experimental/IRGenerator.h +++ b/libsolidity/codegen/experimental/IRGenerator.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include #include @@ -53,24 +54,24 @@ public: m_eofVersion(_eofVersion), m_debugInfoSelection(_debugInfoSelection), m_soliditySourceProvider(_soliditySourceProvider), - m_analysis(_analysis) + m_context{_analysis, {}, {}} {} std::string run( ContractDefinition const& _contract, bytes const& _cborMetadata, std::map const& _otherYulSources - ) const; + ); - std::string generate(ContractDefinition const& _contract) const; - std::string generate(FunctionDefinition const& _function) const; + std::string generate(ContractDefinition const& _contract); + std::string generate(FunctionDefinition const& _function); private: langutil::EVMVersion const m_evmVersion; std::optional const m_eofVersion; OptimiserSettings const m_optimiserSettings; langutil::DebugInfoSelection m_debugInfoSelection = {}; langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; - Analysis const& m_analysis; + IRGenerationContext m_context; }; } diff --git a/libsolidity/codegen/experimental/IRGeneratorForStatements.cpp b/libsolidity/codegen/experimental/IRGeneratorForStatements.cpp index 7051710f2..6dcae3420 100644 --- a/libsolidity/codegen/experimental/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/experimental/IRGeneratorForStatements.cpp @@ -25,6 +25,8 @@ #include +#include + using namespace std; using namespace solidity; using namespace solidity::util; @@ -104,7 +106,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _assembly) CopyTranslate bodyCopier{_assembly.dialect(), _assembly.annotation().externalReferences}; yul::Statement modified = bodyCopier(_assembly.operations()); solAssert(holds_alternative(modified)); - m_code << yul::AsmPrinter()(std::get(modified)); + m_code << yul::AsmPrinter()(std::get(modified)) << "\n"; return false; } @@ -119,6 +121,55 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _variab return false; } +bool IRGeneratorForStatements::visit(ExpressionStatement const&) +{ + return true; +} + +bool IRGeneratorForStatements::visit(Identifier const& _identifier) +{ + auto const* rhsVar = dynamic_cast(_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(&_functionCall.expression()); + solAssert(identifier, "Complex function call expressions not supported."); + auto const* functionDefinition = dynamic_cast(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(&_assignment.leftHandSide()); + solAssert(lhs, "Can only assign to identifiers."); + auto const* lhsVar = dynamic_cast(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&) { solAssert(false, "Unsupported AST node during statement code generation."); diff --git a/libsolidity/codegen/experimental/IRGeneratorForStatements.h b/libsolidity/codegen/experimental/IRGeneratorForStatements.h index 948c6697d..aeca4b5a2 100644 --- a/libsolidity/codegen/experimental/IRGeneratorForStatements.h +++ b/libsolidity/codegen/experimental/IRGeneratorForStatements.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include @@ -30,15 +31,19 @@ class Analysis; class IRGeneratorForStatements: public ASTConstVisitor { public: - IRGeneratorForStatements(Analysis const& _analysis): m_analysis(_analysis) {} + IRGeneratorForStatements(IRGenerationContext& _context): m_context(_context) {} std::string generate(ASTNode const& _node); 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(VariableDeclarationStatement const& _variableDeclarationStatement) override; /// Default visit will reject all AST nodes that are not explicitly supported. bool visitNode(ASTNode const& _node) override; - Analysis const& m_analysis; + IRGenerationContext& m_context; std::stringstream m_code; }; diff --git a/test/libsolidity/semanticTests/experimental/stub.sol b/test/libsolidity/semanticTests/experimental/stub.sol index 5376569a3..6564cd876 100644 --- a/test/libsolidity/semanticTests/experimental/stub.sol +++ b/test/libsolidity/semanticTests/experimental/stub.sol @@ -1,9 +1,7 @@ pragma experimental solidity; -function f(a:word) -> (b:word) { - assembly { - b := a - } +function f(a) -> (b) { + b = a; } contract C { @@ -13,7 +11,7 @@ contract C { assembly { x := 0x42 } - y = x; + y = f(x); assembly { mstore(0, y) return(0, 32) @@ -23,4 +21,4 @@ contract C { // ==== // compileViaYul: true // ---- -// () -> 0x42 +// () -> 21