From b84f1b586e6681605282eb80f25aad040cf20180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Feb 2023 19:53:14 +0100 Subject: [PATCH] User-defined literal suffixes: Code generation --- libsolidity/codegen/ExpressionCompiler.cpp | 98 ++++++++++++--- libsolidity/codegen/ExpressionCompiler.h | 11 +- .../codegen/ir/IRGeneratorForStatements.cpp | 118 +++++++++++++----- .../codegen/ir/IRGeneratorForStatements.h | 7 +- 4 files changed, 185 insertions(+), 49 deletions(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 3ac5eafe8..6efdca2b9 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -701,7 +701,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) appendInternalFunctionCall( function, _functionCall.expression(), - arguments | ranges::views::indirect | ranges::views::addressof | ranges::to() + arguments ); break; } @@ -2310,23 +2310,47 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } -void ExpressionCompiler::endVisit(Literal const& _literal) +bool ExpressionCompiler::visit(Literal const& _literal) { CompilerContext::LocationSetter locationSetter(m_context, _literal); - Type const* type = _literal.annotation().type; - switch (type->category()) + if (_literal.suffixFunction()) { - case Type::Category::RationalNumber: - case Type::Category::Bool: - case Type::Category::Address: - m_context << type->literalValue(&_literal); - break; - case Type::Category::StringLiteral: - break; // will be done during conversion - default: - solUnimplemented("Only integer, boolean and string literals implemented for now."); + FunctionType const& functionType = *_literal.suffixFunction()->functionType(true); + + Expression const* callExpression = std::visit(GenericVisitor{ + [&](ASTPointer const& _identifier) -> Expression const* { return _identifier.get(); }, + [&](ASTPointer const& _memberAccess) -> Expression const* { return _memberAccess.get(); }, + [&](Literal::SubDenomination) -> Expression const* { return nullptr; }, + }, _literal.suffix()); + solAssert(callExpression); + + appendInternalFunctionCall(functionType, *callExpression, _literal); } + else + { + solAssert(_literal.hasSubDenomination() || !_literal.isSuffixed()); + + Type const* type = _literal.annotation().type; + + switch (type->category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + m_context << type->literalValue(&_literal); + break; + case Type::Category::StringLiteral: + break; // will be done during conversion + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } + } + + // Do not visit the suffix just like we do not visit the expression of a normal function call + // that is being called right away. Returning true would run endVisit(Identifier) and push the + // return label again. + return false; } void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation) @@ -2597,7 +2621,10 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons void ExpressionCompiler::appendInternalFunctionCall( FunctionType const& _functionType, Expression const& _callExpression, - vector const& _arguments + variant< + reference_wrapper> const>, + reference_wrapper + > _arguments ) { solAssert(_functionType.kind() == FunctionType::Kind::Internal); @@ -2605,12 +2632,49 @@ void ExpressionCompiler::appendInternalFunctionCall( // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values + TypePointers parameterTypes = _functionType.parameterTypes(); + evmasm::AssemblyItem returnLabel = m_context.pushNewTag(); - for (unsigned i = 0; i < _arguments.size(); ++i) - acceptAndConvert(*_arguments[i], *_functionType.parameterTypes()[i]); + + std::visit(GenericVisitor{ + [&](vector> const& _argumentExpressions) + { + for (unsigned i = 0; i < _argumentExpressions.size(); ++i) + acceptAndConvert(*_argumentExpressions[i], *parameterTypes[i]); + }, + [&](Literal const& _literal) { + solAssert(_literal.suffixFunction()); + + // TODO this is actually not always the right one. + Type const* literalType = TypeProvider::forLiteral(_literal); + + if (parameterTypes.size() == 1) + { + // TODO: We still need to do something for strings, don't we? + // The original TODO said 'reuse the code below', back when this part was in endVisit(Literal) + if (literalType->category() != Type::Category::StringLiteral) + m_context << literalType->literalValue(&_literal); + utils().convertType(*literalType, *parameterTypes.at(0)); + } + else + { + solAssert(parameterTypes.size() == 2); + + auto const* rationalNumberType = dynamic_cast(literalType); + assert(rationalNumberType); + + auto&& [mantissa, exponent] = rationalNumberType->mantissaExponent(); + m_context << mantissa->literalValue(nullptr); + utils().convertType(*mantissa, *parameterTypes.at(0)); + m_context << exponent->literalValue(nullptr); + utils().convertType(*exponent, *parameterTypes.at(1)); + } + }, + }, _arguments); + _callExpression.accept(*this); - unsigned parameterSize = CompilerUtils::sizeOnStack(_functionType.parameterTypes()); + unsigned parameterSize = CompilerUtils::sizeOnStack(parameterTypes); if (_functionType.hasBoundFirstArgument()) { // stack: arg2, ..., argn, label, arg1 diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 31670a841..e5e15e73f 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -90,7 +90,7 @@ private: bool visit(IndexAccess const& _indexAccess) override; bool visit(IndexRangeAccess const& _indexAccess) override; void endVisit(Identifier const& _identifier) override; - void endVisit(Literal const& _literal) override; + bool visit(Literal const& _literal) override; ///@{ ///@name Append code for various operator types @@ -105,10 +105,17 @@ private: /// @} /// Appends code to call an internal function of the given type with the given arguments. + /// The function can handle function calls using the standard call syntax and as a literal suffix. + /// Note that both cases allow for a single literal argument but would handle it differently. + /// The suffix case would split it into mantissa/exponent if the function has two parameters while + /// the standard syntax case would not. void appendInternalFunctionCall( FunctionType const& _functionType, Expression const& _callExpression, - std::vector const& _arguments + std::variant< + std::reference_wrapper> const>, + std::reference_wrapper + > _arguments ); /// Appends code to call a function of the given type with the given arguments. /// @param _tryCall if true, this is the external call of a try statement. In that case, diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e3eb9ef15..12d757779 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1000,11 +1000,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) break; case FunctionType::Kind::Internal: solAssert(functionType); - appendInternalFunctionCall( - _functionCall, - *functionType, - arguments | ranges::views::indirect | ranges::views::addressof | ranges::to - ); + appendInternalFunctionCall(_functionCall, *functionType, arguments); break; case FunctionType::Kind::External: case FunctionType::Kind::DelegateCall: @@ -2464,19 +2460,33 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) bool IRGeneratorForStatements::visit(Literal const& _literal) { setLocation(_literal); - Type const& literalType = type(_literal); - switch (literalType.category()) + if (_literal.suffixFunction()) + appendInternalFunctionCall( + _literal, + *_literal.suffixFunction()->functionType(true /* _internal */), + _literal + ); + else { - case Type::Category::RationalNumber: - case Type::Category::Bool: - case Type::Category::Address: - define(_literal) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n"; - break; - case Type::Category::StringLiteral: - break; // will be done during conversion - default: - solUnimplemented("Only integer, boolean and string literals implemented for now."); + solAssert(_literal.hasSubDenomination() || !_literal.isSuffixed()); + + Type const& expressionType = type(_literal); + switch (expressionType.category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + define(_literal) << toCompactHexWithPrefix(expressionType.literalValue(&_literal)) << "\n"; + break; + case Type::Category::StringLiteral: + // A string literal cannot be simply assigned to a Yul variable so we don't create one here. + // Instead any expression that uses it has to generate custom conversion code that + // depends on where the string ultimately ends up (storage, memory, ABI encoded data, etc.). + break; + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } } return false; } @@ -2511,23 +2521,75 @@ void IRGeneratorForStatements::handleVariableReference( } void IRGeneratorForStatements::appendInternalFunctionCall( - FunctionCall const& _functionCall, + Expression const& _callExpression, FunctionType const& _functionType, - vector const& _arguments + variant< + reference_wrapper> const>, + reference_wrapper + > _arguments ) { solAssert(!_functionType.takesArbitraryParameters()); - FunctionDefinition const *functionDefinition = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract()); - Expression const* identifierOrMemberAccess = &_functionCall.expression(); - vector convertedArguments; - if (_functionType.hasBoundFirstArgument()) - convertedArguments += IRVariable(*identifierOrMemberAccess).part("self").stackSlots(); + FunctionDefinition const* functionDefinition = nullptr; + Expression const* identifierOrMemberAccess = nullptr; + std::visit(GenericVisitor{ + [&](vector> const& _argumentExpressions) + { + auto const* functionCall = dynamic_cast(&_callExpression); + solAssert(functionCall); - TypePointers parameterTypes = _functionType.parameterTypes(); - for (size_t i = 0; i < _arguments.size(); ++i) - convertedArguments += convert(*_arguments[i], *parameterTypes[i]).stackSlots(); + functionDefinition = ASTNode::resolveFunctionCall(*functionCall, &m_context.mostDerivedContract()); + identifierOrMemberAccess = &functionCall->expression(); + + if (_functionType.hasBoundFirstArgument()) + convertedArguments += IRVariable(*identifierOrMemberAccess).part("self").stackSlots(); + + TypePointers parameterTypes = _functionType.parameterTypes(); + for (size_t i = 0; i < _argumentExpressions.size(); ++i) + convertedArguments += convert(*_argumentExpressions[i], *parameterTypes[i]).stackSlots(); + }, + [&](Literal const& _literal) { + auto const* literal = dynamic_cast(&_callExpression); + solAssert(literal); + solAssert(literal->suffixFunction()); + solAssert(!literal->suffixFunction()->virtualSemantics()); + solAssert(!_functionType.hasBoundFirstArgument()); + + functionDefinition = literal->suffixFunction(); + + Type const& literalType = literal->typeOfValue(); + auto const* literalRationalType = dynamic_cast(&literalType); + vector parameterTypes = _functionType.parameterTypes(); + if (parameterTypes.size() == 2) + { + solAssert(literalRationalType); + + auto&& [mantissa, exponent] = literalRationalType->mantissaExponent(); + solAssert(mantissa && exponent); + + IRVariable mantissaVar(m_context.newYulVariable(), *mantissa); + define(mantissaVar) << toCompactHexWithPrefix(mantissa->literalValue(&_literal)) << "\n"; + convertedArguments += convert(mantissaVar, *parameterTypes[0]).stackSlots(); + + IRVariable exponentVar(m_context.newYulVariable(), *exponent); + define(exponentVar) << toCompactHexWithPrefix(exponent->literalValue(&_literal)) << "\n"; + convertedArguments += convert(exponentVar, *parameterTypes[1]).stackSlots(); + } + else + { + solAssert(parameterTypes.size() == 1); + + IRVariable value(m_context.newYulVariable(), literalType); + if (literalType.category() != Type::Category::StringLiteral) + // NOTE: For string literals we do not need to define the variable. The variable + // value will be embedded inside the conversion function. + define(value) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n"; + convertedArguments += convert(value, *parameterTypes[0]).stackSlots(); + } + } + }, _arguments); if (functionDefinition) { @@ -2537,7 +2599,7 @@ void IRGeneratorForStatements::appendInternalFunctionCall( else solAssert(_functionType == *functionDefinition->functionType(true /* _internal */)); - define(_functionCall) << + define(_callExpression) << m_context.enqueueFunctionForCodeGeneration(*functionDefinition) << "(" << joinHumanReadable(convertedArguments) << @@ -2550,7 +2612,7 @@ void IRGeneratorForStatements::appendInternalFunctionCall( YulArity arity = YulArity::fromType(_functionType); m_context.internalFunctionCalledThroughDispatch(arity); - define(_functionCall) << + define(_callExpression) << IRNames::internalDispatch(arity) << "(" << IRVariable(*identifierOrMemberAccess).part("functionIdentifier").name() << diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 8e278f34a..72124ed6c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -155,9 +155,12 @@ private: /// Appends code to call an internal function with the given arguments. /// All involved expressions have already been visited. void appendInternalFunctionCall( - FunctionCall const& _functionCall, + Expression const& _callExpression, FunctionType const& _functionType, - std::vector const& _arguments + std::variant< + std::reference_wrapper> const>, + std::reference_wrapper + > _arguments ); /// Appends code to call an external function with the given arguments. /// All involved expressions have already been visited.