diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 3db857241f28..cfbca3253c5e 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -207,6 +207,8 @@ set(sources experimental/ast/TypeSystemHelper.h experimental/codegen/Common.h experimental/codegen/Common.cpp + experimental/codegen/IRVariable.cpp + experimental/codegen/IRVariable.h experimental/codegen/IRGenerationContext.h experimental/codegen/IRGenerator.cpp experimental/codegen/IRGenerator.h diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 27926721aa6f..196dd4f1cfd3 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -2162,6 +2162,9 @@ class BinaryOperation: public Expression Expression const& rightExpression() const { return *m_right; } Token getOperator() const { return m_operator; } + /// @returns the given arguments in the order they were written. + std::vector> arguments() const { return {m_left, m_right}; } + FunctionType const* userDefinedFunctionType() const; BinaryOperationAnnotation& annotation() const override; diff --git a/libsolidity/experimental/analysis/TypeInference.cpp b/libsolidity/experimental/analysis/TypeInference.cpp index 6913dc0bf7cd..3f5fbb6533c2 100644 --- a/libsolidity/experimental/analysis/TypeInference.cpp +++ b/libsolidity/experimental/analysis/TypeInference.cpp @@ -837,6 +837,7 @@ bool TypeInference::visit(TypeDefinition const& _typeDefinition) members->second.emplace("abs", TypeMember{helper.functionType(*underlyingType, definedType)}); members->second.emplace("rep", TypeMember{helper.functionType(definedType, *underlyingType)}); + annotation().underlyingTypes[constructor] = *underlyingType; } if (helper.isPrimitiveType(definedType, PrimitiveType::Pair)) diff --git a/libsolidity/experimental/analysis/TypeInference.h b/libsolidity/experimental/analysis/TypeInference.h index 093236799555..0493c233bca8 100644 --- a/libsolidity/experimental/analysis/TypeInference.h +++ b/libsolidity/experimental/analysis/TypeInference.h @@ -50,6 +50,7 @@ class TypeInference: public ASTConstVisitor std::map> typeClassFunctions; std::map> operators; std::map> members; + std::map underlyingTypes; }; bool visit(Block const&) override { return true; } bool visit(VariableDeclarationStatement const&) override { return true; } diff --git a/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp b/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp index cb4705f0d189..90d2de991431 100644 --- a/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp +++ b/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp @@ -33,6 +33,7 @@ #include #include +#include using namespace solidity; using namespace solidity::util; @@ -101,7 +102,7 @@ struct CopyTranslate: public yul::ASTCopier auto type = m_context.analysis.annotation(*varDecl).type; solAssert(type); solAssert(m_context.env->typeEquals(*type, m_context.analysis.typeSystem().type(PrimitiveType::Word, {}))); - std::string value = IRNames::localVariable(*varDecl); + std::string value = IRVariable{*varDecl, *type, IRGeneratorForStatements::stackSize(m_context, *type)}.name(); return yul::Identifier{_identifier.debugData, yul::YulString{value}}; } @@ -112,6 +113,67 @@ struct CopyTranslate: public yul::ASTCopier } +std::size_t IRGeneratorForStatements::stackSize(IRGenerationContext const& _context, Type _type) +{ + TypeSystemHelpers helper{_context.analysis.typeSystem()}; + _type = _context.env->resolve(_type); + solAssert(std::holds_alternative(_type), "No monomorphized type."); + + // type -> # stack slots + // unit, itself -> 0 + // void, literals(integer), typeFunction -> error (maybe generate a revert) + // word, bool, function -> 1 + // pair -> sum(stackSize(args)) + // user-defined -> stackSize(underlying type) + TypeConstant typeConstant = std::get(_type); + if ( + helper.isPrimitiveType(_type, PrimitiveType::Unit) || + helper.isPrimitiveType(_type, PrimitiveType::Itself) + ) + return 0; + else if ( + helper.isPrimitiveType(_type, PrimitiveType::Bool) || + helper.isPrimitiveType(_type, PrimitiveType::Word) + ) + { + solAssert(typeConstant.arguments.empty(), "Primitive type Bool or Word should have no arguments."); + return 1; + } + else if (helper.isFunctionType(_type)) + return 1; + else if ( + helper.isPrimitiveType(_type, PrimitiveType::Integer) || + helper.isPrimitiveType(_type, PrimitiveType::Void) || + helper.isPrimitiveType(_type, PrimitiveType::TypeFunction) + ) + solAssert(false, "Attempted to query the stack size of a type without stack representation."); + else if (helper.isPrimitiveType(_type, PrimitiveType::Pair)) + { + solAssert(typeConstant.arguments.size() == 2); + return stackSize(_context, typeConstant.arguments.front()) + stackSize(_context, typeConstant.arguments.back()); + } + else + { + Type underlyingType = _context.env->resolve( + _context.analysis.annotation().underlyingTypes.at(typeConstant.constructor)); + if (helper.isTypeConstant(underlyingType)) + return stackSize(_context, underlyingType); + + TypeEnvironment env = _context.env->clone(); + Type genericFunctionType = helper.typeFunctionType( + helper.tupleType(typeConstant.arguments), + env.typeSystem().freshTypeVariable({})); + solAssert(env.unify(genericFunctionType, underlyingType).empty()); + + Type resolvedType = env.resolveRecursive(genericFunctionType); + auto [argumentType, resultType] = helper.destTypeFunctionType(resolvedType); + return stackSize(_context, resultType); + } + + //TODO: sum types + return 0; +} + bool IRGeneratorForStatements::visit(TupleExpression const& _tupleExpression) { std::vector components; @@ -119,7 +181,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tupleExpression) { solUnimplementedAssert(component); component->accept(*this); - components.emplace_back(IRNames::localVariable(*component)); + components.emplace_back(var(*component).commaSeparatedList()); } solUnimplementedAssert(false, "No support for tuples."); @@ -144,10 +206,11 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _variab VariableDeclaration const* variableDeclaration = _variableDeclarationStatement.declarations().front().get(); solAssert(variableDeclaration); // TODO: check the type of the variable; register local variable; initialize - m_code << "let " << IRNames::localVariable(*variableDeclaration); if (_variableDeclarationStatement.initialValue()) - m_code << " := " << IRNames::localVariable(*_variableDeclarationStatement.initialValue()); - m_code << "\n"; + define(var(*variableDeclaration), var(*_variableDeclarationStatement.initialValue())); + else + declare(var(*variableDeclaration)); + return false; } @@ -158,10 +221,8 @@ bool IRGeneratorForStatements::visit(ExpressionStatement const&) bool IRGeneratorForStatements::visit(Identifier const& _identifier) { - if (auto const* var = dynamic_cast(_identifier.annotation().referencedDeclaration)) - { - m_code << "let " << IRNames::localVariable(_identifier) << " := " << IRNames::localVariable(*var) << "\n"; - } + if (auto const* variable = dynamic_cast(_identifier.annotation().referencedDeclaration)) + define(var(_identifier), var(*variable)); else if (auto const* function = dynamic_cast(_identifier.annotation().referencedDeclaration)) solAssert(m_expressionDeclaration.emplace(&_identifier, function).second); else if (auto const* typeClass = dynamic_cast(_identifier.annotation().referencedDeclaration)) @@ -179,7 +240,8 @@ void IRGeneratorForStatements::endVisit(Return const& _return) { solAssert(_return.annotation().function, "Invalid return."); solAssert(_return.annotation().function->experimentalReturnExpression(), "Invalid return."); - m_code << IRNames::localVariable(*_return.annotation().function->experimentalReturnExpression()) << " := " << IRNames::localVariable(*value) << "\n"; + auto returnExpression = _return.annotation().function->experimentalReturnExpression(); + assign(var(*returnExpression), var(*value)); } m_code << "leave\n"; @@ -201,13 +263,44 @@ void IRGeneratorForStatements::endVisit(BinaryOperation const& _binaryOperation) Type functionType = helper.functionType(helper.tupleType({leftType, rightType}), resultType); auto [typeClass, memberName] = m_context.analysis.annotation().operators.at(_binaryOperation.getOperator()); auto const& functionDefinition = resolveTypeClassFunction(typeClass, memberName, functionType); - // TODO: deduplicate with FunctionCall + std::string result = var(_binaryOperation).commaSeparatedList(); + if (!result.empty()) + m_code << "let " << result << " := "; + m_code << buildFunctionCall(functionDefinition, functionType, _binaryOperation.arguments()); +} + +std::string IRGeneratorForStatements::buildFunctionCall(FunctionDefinition const& _functionDefinition, Type _functionType, std::vector> const& _arguments) +{ + // Ensure type is resolved // TODO: get around resolveRecursive by passing the environment further down? - functionType = m_context.env->resolveRecursive(functionType); - m_context.enqueueFunctionDefinition(&functionDefinition, functionType); - // TODO: account for return stack size - m_code << "let " << IRNames::localVariable(_binaryOperation) << " := " << IRNames::function(*m_context.env, functionDefinition, functionType) << "(" - << IRNames::localVariable(_binaryOperation.leftExpression()) << ", " << IRNames::localVariable(_binaryOperation.rightExpression()) << ")\n"; + Type resolvedFunctionType = m_context.env->resolveRecursive(_functionType); + m_context.enqueueFunctionDefinition(&_functionDefinition, resolvedFunctionType); + + std::ostringstream output; + output << IRNames::function(*m_context.env, _functionDefinition, resolvedFunctionType) << "("; + if (_arguments.size() == 1) + output << var(*_arguments.back()).commaSeparatedList(); + else if (_arguments.size() > 1) + { + for (auto arg: _arguments | ranges::views::drop_last(1)) + output << var(*arg).commaSeparatedList(); + output << var(*_arguments.back()).commaSeparatedListPrefixed(); + } + output << ")\n"; + return output.str(); +} + +void IRGeneratorForStatements::assign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare) +{ + solAssert(stackSize(m_context, _lhs.type()) == stackSize(m_context, _rhs.type())); + for (auto&& [lhsSlot, rhsSlot]: ranges::zip_view(_lhs.stackSlots(), _rhs.stackSlots())) + m_code << (_declare ? "let " : "") << lhsSlot << " := " << rhsSlot << "\n"; +} + +void IRGeneratorForStatements::declare(IRVariable const& _var) +{ + if (_var.stackSize() > 0) + m_code << "let " << _var.commaSeparatedList() << "\n"; } namespace @@ -308,32 +401,23 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) case Builtins::FromBool: case Builtins::Identity: solAssert(_functionCall.arguments().size() == 1); - m_code << "let " << IRNames::localVariable(_functionCall) << " := " << IRNames::localVariable(*_functionCall.arguments().front()) << "\n"; + define(var(_functionCall), var(*_functionCall.arguments().front())); return; case Builtins::ToBool: solAssert(_functionCall.arguments().size() == 1); - m_code << "let " << IRNames::localVariable(_functionCall) << " := iszero(iszero(" << IRNames::localVariable(*_functionCall.arguments().front()) << "))\n"; + m_code << "let " << var(_functionCall).name() << " := iszero(iszero(" << var(*_functionCall.arguments().front()).name() << "))\n"; return; } solAssert(false); } FunctionDefinition const* functionDefinition = dynamic_cast(std::get(declaration)); solAssert(functionDefinition); - // TODO: get around resolveRecursive by passing the environment further down? - functionType = m_context.env->resolveRecursive(functionType); - m_context.enqueueFunctionDefinition(functionDefinition, functionType); // TODO: account for return stack size solAssert(!functionDefinition->returnParameterList()); - if (functionDefinition->experimentalReturnExpression()) - m_code << "let " << IRNames::localVariable(_functionCall) << " := "; - m_code << IRNames::function(*m_context.env, *functionDefinition, functionType) << "("; - auto const& arguments = _functionCall.arguments(); - if (arguments.size() > 1) - for (auto arg: arguments | ranges::views::drop_last(1)) - m_code << IRNames::localVariable(*arg) << ", "; - if (!arguments.empty()) - m_code << IRNames::localVariable(*arguments.back()); - m_code << ")\n"; + std::string result = var(_functionCall).commaSeparatedList(); + if (!result.empty()) + m_code << "let " << result << " := "; + m_code << buildFunctionCall(*functionDefinition, functionType, _functionCall.arguments()); } bool IRGeneratorForStatements::visit(FunctionCall const&) @@ -356,7 +440,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) _ifStatement.condition().accept(*this); if (_ifStatement.falseStatement()) { - m_code << "switch " << IRNames::localVariable(_ifStatement.condition()) << " {\n"; + m_code << "switch " << var(_ifStatement.condition()).name() << " {\n"; m_code << "case 0 {\n"; _ifStatement.falseStatement()->accept(*this); m_code << "}\n"; @@ -366,7 +450,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) } else { - m_code << "if " << IRNames::localVariable(_ifStatement.condition()) << " {\n"; + m_code << "if " << var(_ifStatement.condition()).name() << " {\n"; _ifStatement.trueStatement().accept(*this); m_code << "}\n"; } @@ -380,9 +464,8 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) 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"; + assign(var(*lhsVar), var(_assignment.rightHandSide())); + define(var(_assignment), var(*lhsVar)); return false; } diff --git a/libsolidity/experimental/codegen/IRGeneratorForStatements.h b/libsolidity/experimental/codegen/IRGeneratorForStatements.h index 527a6abe8562..34b70244197a 100644 --- a/libsolidity/experimental/codegen/IRGeneratorForStatements.h +++ b/libsolidity/experimental/codegen/IRGeneratorForStatements.h @@ -19,6 +19,8 @@ #pragma once #include +#include + #include #include @@ -34,6 +36,7 @@ class IRGeneratorForStatements: public ASTConstVisitor IRGeneratorForStatements(IRGenerationContext& _context): m_context(_context) {} std::string generate(ASTNode const& _node); + static std::size_t stackSize(IRGenerationContext const& _context, Type _type); private: bool visit(ExpressionStatement const& _expressionStatement) override; bool visit(Block const& _block) override; @@ -54,6 +57,14 @@ class IRGeneratorForStatements: public ASTConstVisitor void endVisit(Return const& _return) override; /// Default visit will reject all AST nodes that are not explicitly supported. bool visitNode(ASTNode const& _node) override; + + /// Defines @a _var using the value of @a _value. It declares and assign the variable. + void define(IRVariable const& _var, IRVariable const& _value) { assign(_var, _value, true); } + /// Assigns @a _var to the value of @a _value. It does not declare the variable. + void assign(IRVariable const& _var, IRVariable const& _value, bool _declare = false); + /// Declares variable @a _var. + void declare(IRVariable const& _var); + IRGenerationContext& m_context; std::stringstream m_code; enum class Builtins @@ -63,6 +74,15 @@ class IRGeneratorForStatements: public ASTConstVisitor ToBool }; std::map> m_expressionDeclaration; + + std::string buildFunctionCall(FunctionDefinition const& _functionDefinition, Type _functionType, std::vector> const& _arguments); + + template + IRVariable var(IRVariableType const& _var) const + { + return IRVariable(_var, type(_var), stackSize(m_context, type(_var))); + } + Type type(ASTNode const& _node) const; FunctionDefinition const& resolveTypeClassFunction(TypeClass _class, std::string _name, Type _type); diff --git a/libsolidity/experimental/codegen/IRVariable.cpp b/libsolidity/experimental/codegen/IRVariable.cpp new file mode 100644 index 000000000000..c3d34005f933 --- /dev/null +++ b/libsolidity/experimental/codegen/IRVariable.cpp @@ -0,0 +1,87 @@ +/* + 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 + +#include +#include +#include +#include + +#include + +using namespace solidity; +using namespace solidity::util; +using namespace solidity::frontend::experimental; + +IRVariable::IRVariable(std::string _baseName, Type _type, size_t _stackSize): + m_baseName(std::move(_baseName)), m_type(_type), m_stackSize(_stackSize) +{ +} + +IRVariable::IRVariable(VariableDeclaration const& _declaration, Type _type, size_t _stackSize): + IRVariable(IRNames::localVariable(_declaration), _type, _stackSize) +{ + solAssert(!_declaration.isStateVariable(), ""); +} + +IRVariable::IRVariable(Expression const& _expression, Type _type, size_t _stackSize): + IRVariable(IRNames::localVariable(_expression), _type, _stackSize) +{ +} + +std::vector IRVariable::stackSlots() const +{ + std::vector result; + result.reserve(m_stackSize); + if (m_stackSize == 1) + result.emplace_back(m_baseName); + else + for (size_t i = 0; i < m_stackSize; ++i) + result.emplace_back(suffixedName(std::to_string(i))); + return result; +} + +std::string IRVariable::commaSeparatedList() const +{ + return joinHumanReadable(stackSlots()); +} + +std::string IRVariable::commaSeparatedListPrefixed() const +{ + return joinHumanReadablePrefixed(stackSlots()); +} + +std::string IRVariable::name() const +{ + solAssert(m_stackSize == 1, ""); + return m_baseName; +} + +IRVariable IRVariable::tupleComponent(size_t) const +{ + // TODO + solAssert(false, "IRVariable::tupleComponent Not implemented"); + return IRVariable{"", Type{}, 1}; +} + +std::string IRVariable::suffixedName(std::string const& _suffix) const +{ + if (_suffix.empty()) + return m_baseName; + else + return m_baseName + '_' + _suffix; +} diff --git a/libsolidity/experimental/codegen/IRVariable.h b/libsolidity/experimental/codegen/IRVariable.h new file mode 100644 index 000000000000..be11ddfa6635 --- /dev/null +++ b/libsolidity/experimental/codegen/IRVariable.h @@ -0,0 +1,77 @@ +/* + 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 + +#include +#include +#include + +namespace solidity::frontend::experimental +{ +/** + * An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression + * of a specific Solidity type. + */ +class IRVariable +{ + +public: + /// IR variable with explicit base name @a _baseName, type @a _type and stack size @_stackSize. + IRVariable(std::string _baseName, Type _type, size_t _stackSize); + /// IR variable referring to the declaration @a _declaration. + explicit IRVariable(VariableDeclaration const& _declaration, Type _type, size_t _stackSize); + /// IR variable referring to the expression @a _expression. + IRVariable(Expression const& _expression, Type _type, size_t _stackSize); + + /// @returns the name of the variable, if it occupies a single stack slot (otherwise throws). + std::string name() const; + + /// @returns a comma-separated list of the stack slots of the variable. + std::string commaSeparatedList() const; + + /// @returns a comma-separated list of the stack slots of the variable that is + /// prefixed with a comma, unless it is empty. + std::string commaSeparatedListPrefixed() const; + + /// @returns an IRVariable referring to the tuple component @a _i of a tuple variable. + IRVariable tupleComponent(std::size_t _i) const; + + /// @returns the type of the variable. + Type type() const { return m_type; } + + /// @returns the stack size of the variable. + size_t stackSize() const { return m_stackSize; } + + /// @returns a vector containing the names of the stack slots of the variable. + std::vector stackSlots() const; + +private: + /// @returns a name consisting of the base name appended with an underscore and @a _suffix, + /// unless @a _suffix is empty, in which case the base name itself is returned. + std::string suffixedName(std::string const& _suffix) const; + std::string m_baseName; + Type m_type; + size_t m_stackSize; +}; + +} diff --git a/test/libsolidity/syntaxTests/experimental/builtin/builtin_type_definition.sol b/test/libsolidity/syntaxTests/experimental/builtin/builtin_type_definition.sol index 373d76c339ff..258e55b779a1 100644 --- a/test/libsolidity/syntaxTests/experimental/builtin/builtin_type_definition.sol +++ b/test/libsolidity/syntaxTests/experimental/builtin/builtin_type_definition.sol @@ -12,7 +12,7 @@ type pair(T, U) = __builtin("pair"); contract C { fallback() external { - let v: void; + let v: void->void; let b: bool; bool.abs(bool.rep(b));