User-defined literal suffixes: Code generation

This commit is contained in:
Kamil Śliwak 2023-02-24 19:53:14 +01:00
parent 143722c705
commit b84f1b586e
4 changed files with 185 additions and 49 deletions

View File

@ -701,7 +701,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
appendInternalFunctionCall(
function,
_functionCall.expression(),
arguments | ranges::views::indirect | ranges::views::addressof | ranges::to<vector>()
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<Identifier> const& _identifier) -> Expression const* { return _identifier.get(); },
[&](ASTPointer<MemberAccess> 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<Expression const*> const& _arguments
variant<
reference_wrapper<vector<ASTPointer<Expression const>> const>,
reference_wrapper<Literal const>
> _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<ASTPointer<Expression const>> 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<RationalNumberType const*>(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

View File

@ -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<Expression const*> const& _arguments
std::variant<
std::reference_wrapper<std::vector<ASTPointer<Expression const>> const>,
std::reference_wrapper<Literal const>
> _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,

View File

@ -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<vector>
);
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<Expression const*> const& _arguments
variant<
reference_wrapper<vector<ASTPointer<Expression const>> const>,
reference_wrapper<Literal const>
> _arguments
)
{
solAssert(!_functionType.takesArbitraryParameters());
FunctionDefinition const *functionDefinition = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract());
Expression const* identifierOrMemberAccess = &_functionCall.expression();
vector<string> convertedArguments;
if (_functionType.hasBoundFirstArgument())
convertedArguments += IRVariable(*identifierOrMemberAccess).part("self").stackSlots();
FunctionDefinition const* functionDefinition = nullptr;
Expression const* identifierOrMemberAccess = nullptr;
std::visit(GenericVisitor{
[&](vector<ASTPointer<Expression const>> const& _argumentExpressions)
{
auto const* functionCall = dynamic_cast<FunctionCall const*>(&_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<Literal const*>(&_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<RationalNumberType const*>(&literalType);
vector<Type const*> 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() <<

View File

@ -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<Expression const *> const& _arguments
std::variant<
std::reference_wrapper<std::vector<ASTPointer<Expression const>> const>,
std::reference_wrapper<Literal const>
> _arguments
);
/// Appends code to call an external function with the given arguments.
/// All involved expressions have already been visited.