/*
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
/**
* Component that translates Solidity code into Yul at statement level and below.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace std::string_literals;
namespace
{
struct CopyTranslate: public yul::ASTCopier
{
using ExternalRefsMap = std::map;
CopyTranslate(yul::Dialect const& _dialect, IRGenerationContext& _context, ExternalRefsMap const& _references):
m_dialect(_dialect), m_context(_context), m_references(_references) {}
using ASTCopier::operator();
yul::Expression operator()(yul::Identifier const& _identifier) override
{
// The operator() function is only called in lvalue context. In rvalue context,
// only translate(yul::Identifier) is called.
if (m_references.count(&_identifier))
return translateReference(_identifier);
else
return ASTCopier::operator()(_identifier);
}
yul::YulString translateIdentifier(yul::YulString _name) override
{
// Strictly, the dialect used by inline assembly (m_dialect) could be different
// from the Yul dialect we are compiling to. So we are assuming here that the builtin
// functions are identical. This should not be a problem for now since everything
// is EVM anyway.
if (m_dialect.builtin(_name))
return _name;
else
return yul::YulString{"usr$" + _name.str()};
}
yul::Identifier translate(yul::Identifier const& _identifier) override
{
if (!m_references.count(&_identifier))
return ASTCopier::translate(_identifier);
yul::Expression translated = translateReference(_identifier);
solAssert(holds_alternative(translated));
return get(std::move(translated));
}
private:
/// Translates a reference to a local variable, potentially including
/// a suffix. Might return a literal, which causes this to be invalid in
/// lvalue-context.
yul::Expression translateReference(yul::Identifier const& _identifier)
{
auto const& reference = m_references.at(&_identifier);
auto const varDecl = dynamic_cast(reference.declaration);
solUnimplementedAssert(varDecl);
string const& suffix = reference.suffix;
string value;
if (suffix.empty() && varDecl->isLocalVariable())
{
auto const& var = m_context.localVariable(*varDecl);
solAssert(var.type().sizeOnStack() == 1);
value = var.commaSeparatedList();
}
else if (varDecl->isConstant())
{
VariableDeclaration const* variable = rootConstVariableDeclaration(*varDecl);
solAssert(variable);
if (variable->value()->annotation().type->category() == Type::Category::RationalNumber)
{
u256 intValue = dynamic_cast(*variable->value()->annotation().type).literalValue(nullptr);
if (auto const* bytesType = dynamic_cast(variable->type()))
intValue <<= 256 - 8 * bytesType->numBytes();
else
solAssert(variable->type()->category() == Type::Category::Integer);
value = intValue.str();
}
else if (auto const* literal = dynamic_cast(variable->value().get()))
{
Type const* type = literal->annotation().type;
switch (type->category())
{
case Type::Category::Bool:
case Type::Category::Address:
solAssert(type->category() == variable->annotation().type->category());
value = toCompactHexWithPrefix(type->literalValue(literal));
break;
case Type::Category::StringLiteral:
{
auto const& stringLiteral = dynamic_cast(*type);
solAssert(variable->type()->category() == Type::Category::FixedBytes);
unsigned const numBytes = dynamic_cast(*variable->type()).numBytes();
solAssert(stringLiteral.value().size() <= numBytes);
value = formatNumber(u256(h256(stringLiteral.value(), h256::AlignLeft)));
break;
}
default:
solAssert(false);
}
}
else
solAssert(false, "Invalid constant in inline assembly.");
}
else if (varDecl->isStateVariable())
{
if (suffix == "slot")
value = m_context.storageLocationOfStateVariable(*varDecl).first.str();
else if (suffix == "offset")
value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
else
solAssert(false);
}
else if (varDecl->type()->dataStoredIn(DataLocation::Storage))
{
solAssert(suffix == "slot" || suffix == "offset");
solAssert(varDecl->isLocalVariable());
if (suffix == "slot")
value = IRVariable{*varDecl}.part("slot").name();
else if (varDecl->type()->isValueType())
value = IRVariable{*varDecl}.part("offset").name();
else
{
solAssert(!IRVariable{*varDecl}.hasPart("offset"));
value = "0";
}
}
else if (varDecl->type()->dataStoredIn(DataLocation::CallData))
{
solAssert(suffix == "offset" || suffix == "length");
value = IRVariable{*varDecl}.part(suffix).name();
}
else if (
auto const* functionType = dynamic_cast(varDecl->type());
functionType && functionType->kind() == FunctionType::Kind::External
)
{
solAssert(suffix == "selector" || suffix == "address");
solAssert(varDecl->type()->sizeOnStack() == 2);
if (suffix == "selector")
value = IRVariable{*varDecl}.part("functionSelector").name();
else
value = IRVariable{*varDecl}.part("address").name();
}
else
solAssert(false);
if (isDigit(value.front()))
return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}};
else
return yul::Identifier{_identifier.debugData, yul::YulString{value}};
}
yul::Dialect const& m_dialect;
IRGenerationContext& m_context;
ExternalRefsMap const& m_references;
};
}
string IRGeneratorForStatementsBase::code() const
{
return m_code.str();
}
std::ostringstream& IRGeneratorForStatementsBase::appendCode(bool _addLocationComment)
{
if (
_addLocationComment &&
m_currentLocation.isValid() &&
m_lastLocation != m_currentLocation
)
m_code << dispenseLocationComment(m_currentLocation, m_context) << "\n";
m_lastLocation = m_currentLocation;
return m_code;
}
void IRGeneratorForStatementsBase::setLocation(ASTNode const& _node)
{
m_currentLocation = _node.location();
}
string IRGeneratorForStatements::code() const
{
solAssert(!m_currentLValue, "LValue not reset!");
return IRGeneratorForStatementsBase::code();
}
void IRGeneratorForStatements::generate(Block const& _block)
{
try
{
_block.accept(*this);
}
catch (langutil::UnimplementedFeatureError const& _error)
{
if (!boost::get_error_info(_error))
_error << langutil::errinfo_sourceLocation(m_currentLocation);
BOOST_THROW_EXCEPTION(_error);
}
}
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
{
try
{
setLocation(_varDecl);
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
solAssert(!_varDecl.isConstant());
if (!_varDecl.value())
return;
_varDecl.value()->accept(*this);
writeToLValue(
_varDecl.immutable() ?
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
m_context.storageLocationOfStateVariable(_varDecl).second
}},
*_varDecl.value()
);
}
catch (langutil::UnimplementedFeatureError const& _error)
{
if (!boost::get_error_info(_error))
_error << langutil::errinfo_sourceLocation(m_currentLocation);
BOOST_THROW_EXCEPTION(_error);
}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
try
{
setLocation(_varDecl);
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
auto const* type = _varDecl.type();
if (dynamic_cast(type))
return;
else if (auto const* refType = dynamic_cast(type))
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
return;
IRVariable zero = zeroValue(*type);
assign(m_context.localVariable(_varDecl), zero);
}
catch (langutil::UnimplementedFeatureError const& _error)
{
if (!boost::get_error_info(_error))
_error << langutil::errinfo_sourceLocation(m_currentLocation);
BOOST_THROW_EXCEPTION(_error);
}
}
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
{
try
{
setLocation(_expression);
_expression.accept(*this);
setLocation(_expression);
IRVariable variable{m_context.newYulVariable(), _targetType};
define(variable, _expression);
return variable;
}
catch (langutil::UnimplementedFeatureError const& _error)
{
if (!boost::get_error_info(_error))
_error << langutil::errinfo_sourceLocation(m_currentLocation);
BOOST_THROW_EXCEPTION(_error);
}
}
string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant)
{
try
{
string functionName = IRNames::constantValueFunction(_constant);
return m_context.functionCollector().createFunction(functionName, [&] {
Whiskers templ(R"(
function () -> {
:=
}
)");
templ("sourceLocationComment", dispenseLocationComment(_constant, m_context));
templ("functionName", functionName);
IRGeneratorForStatements generator(m_context, m_utils);
solAssert(_constant.value());
Type const& constantType = *_constant.type();
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
templ("code", generator.code());
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
return templ.render();
});
}
catch (langutil::UnimplementedFeatureError const& _error)
{
if (!boost::get_error_info(_error))
_error << langutil::errinfo_sourceLocation(m_currentLocation);
BOOST_THROW_EXCEPTION(_error);
}
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
setLocation(_varDeclStatement);
if (Expression const* expression = _varDeclStatement.initialValue())
{
if (_varDeclStatement.declarations().size() > 1)
{
auto const* tupleType = dynamic_cast(expression->annotation().type);
solAssert(tupleType, "Expected expression of tuple type.");
solAssert(_varDeclStatement.declarations().size() == tupleType->components().size(), "Invalid number of tuple components.");
for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i)
if (auto const& decl = _varDeclStatement.declarations()[i])
{
solAssert(tupleType->components()[i]);
define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i));
}
}
else
{
VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front();
define(m_context.addLocalVariable(varDecl), *expression);
}
}
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
{
declare(m_context.addLocalVariable(*decl));
initializeLocalVar(*decl);
}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
{
_conditional.condition().accept(*this);
setLocation(_conditional);
string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean());
declare(_conditional);
appendCode() << "switch " << condition << "\n" "case 0 {\n";
_conditional.falseExpression().accept(*this);
setLocation(_conditional);
assign(_conditional, _conditional.falseExpression());
appendCode() << "}\n" "default {\n";
_conditional.trueExpression().accept(*this);
setLocation(_conditional);
assign(_conditional, _conditional.trueExpression());
appendCode() << "}\n";
return false;
}
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
_assignment.rightHandSide().accept(*this);
setLocation(_assignment);
Token assignmentOperator = _assignment.assignmentOperator();
Token binaryOperator =
assignmentOperator == Token::Assign ?
assignmentOperator :
TokenTraits::AssignmentToBinaryOp(assignmentOperator);
if (TokenTraits::isShiftOp(binaryOperator))
solAssert(type(_assignment.rightHandSide()).mobileType());
IRVariable value =
type(_assignment.leftHandSide()).isValueType() ?
convert(
_assignment.rightHandSide(),
TokenTraits::isShiftOp(binaryOperator) ? *type(_assignment.rightHandSide()).mobileType() : type(_assignment)
) :
_assignment.rightHandSide();
_assignment.leftHandSide().accept(*this);
solAssert(!!m_currentLValue, "LValue not retrieved.");
setLocation(_assignment);
if (assignmentOperator != Token::Assign)
{
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
solAssert(binaryOperator != Token::Exp);
solAssert(type(_assignment) == type(_assignment.leftHandSide()));
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
solAssert(type(_assignment) == leftIntermediate.type());
define(_assignment) << (
TokenTraits::isShiftOp(binaryOperator) ?
shiftOperation(binaryOperator, leftIntermediate, value) :
binaryOperation(binaryOperator, type(_assignment), leftIntermediate.name(), value.name())
) << "\n";
writeToLValue(*m_currentLValue, IRVariable(_assignment));
}
else
{
writeToLValue(*m_currentLValue, value);
if (dynamic_cast(&m_currentLValue->type))
define(_assignment, readFromLValue(*m_currentLValue));
else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
define(_assignment, value);
}
m_currentLValue.reset();
return false;
}
bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
{
setLocation(_tuple);
if (_tuple.isInlineArray())
{
auto const& arrayType = dynamic_cast(*_tuple.annotation().type);
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
define(_tuple) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
"(" <<
_tuple.components().size() <<
")\n";
string mpos = IRVariable(_tuple).part("mpos").name();
Type const& baseType = *arrayType.baseType();
for (size_t i = 0; i < _tuple.components().size(); i++)
{
Expression const& component = *_tuple.components()[i];
component.accept(*this);
setLocation(_tuple);
IRVariable converted = convert(component, baseType);
appendCode() <<
m_utils.writeToMemoryFunction(baseType) <<
"(" <<
("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") <<
", " <<
converted.commaSeparatedList() <<
")\n";
}
}
else
{
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
if (willBeWrittenTo)
solAssert(!m_currentLValue);
if (_tuple.components().size() == 1)
{
solAssert(_tuple.components().front());
_tuple.components().front()->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
solAssert(!!m_currentLValue);
else
define(_tuple, *_tuple.components().front());
}
else
{
vector> lvalues;
for (size_t i = 0; i < _tuple.components().size(); ++i)
if (auto const& component = _tuple.components()[i])
{
component->accept(*this);
setLocation(_tuple);
if (willBeWrittenTo)
{
solAssert(!!m_currentLValue);
lvalues.emplace_back(std::move(m_currentLValue));
m_currentLValue.reset();
}
else
define(IRVariable(_tuple).tupleComponent(i), *component);
}
else if (willBeWrittenTo)
lvalues.emplace_back();
if (_tuple.annotation().willBeWrittenTo)
m_currentLValue.emplace(IRLValue{
*_tuple.annotation().type,
IRLValue::Tuple{std::move(lvalues)}
});
}
}
return false;
}
bool IRGeneratorForStatements::visit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Checked);
m_context.setArithmetic(Arithmetic::Wrapping);
}
return true;
}
void IRGeneratorForStatements::endVisit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Wrapping);
m_context.setArithmetic(Arithmetic::Checked);
}
}
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
{
_ifStatement.condition().accept(*this);
setLocation(_ifStatement);
string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean());
if (_ifStatement.falseStatement())
{
appendCode() << "switch " << condition << "\n" "case 0 {\n";
_ifStatement.falseStatement()->accept(*this);
setLocation(_ifStatement);
appendCode() << "}\n" "default {\n";
}
else
appendCode() << "if " << condition << " {\n";
_ifStatement.trueStatement().accept(*this);
setLocation(_ifStatement);
appendCode() << "}\n";
return false;
}
void IRGeneratorForStatements::endVisit(PlaceholderStatement const& _placeholder)
{
solAssert(m_placeholderCallback);
setLocation(_placeholder);
appendCode() << m_placeholderCallback();
}
bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
{
setLocation(_forStatement);
generateLoop(
_forStatement.body(),
_forStatement.condition(),
_forStatement.initializationExpression(),
_forStatement.loopExpression()
);
return false;
}
bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
{
setLocation(_whileStatement);
generateLoop(
_whileStatement.body(),
&_whileStatement.condition(),
nullptr,
nullptr,
_whileStatement.isDoWhile()
);
return false;
}
bool IRGeneratorForStatements::visit(Continue const& _continue)
{
setLocation(_continue);
appendCode() << "continue\n";
return false;
}
bool IRGeneratorForStatements::visit(Break const& _break)
{
setLocation(_break);
appendCode() << "break\n";
return false;
}
void IRGeneratorForStatements::endVisit(Return const& _return)
{
setLocation(_return);
if (Expression const* value = _return.expression())
{
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
vector> const& returnParameters =
_return.annotation().functionReturnParameters->parameters();
if (returnParameters.size() > 1)
for (size_t i = 0; i < returnParameters.size(); ++i)
assign(m_context.localVariable(*returnParameters[i]), IRVariable(*value).tupleComponent(i));
else if (returnParameters.size() == 1)
assign(m_context.localVariable(*returnParameters.front()), *value);
}
appendCode() << "leave\n";
}
bool IRGeneratorForStatements::visit(UnaryOperation const& _unaryOperation)
{
setLocation(_unaryOperation);
Type const& resultType = type(_unaryOperation);
Token const op = _unaryOperation.getOperator();
if (resultType.category() == Type::Category::RationalNumber)
{
define(_unaryOperation) << formatNumber(resultType.literalValue(nullptr)) << "\n";
return false;
}
_unaryOperation.subExpression().accept(*this);
setLocation(_unaryOperation);
if (op == Token::Delete)
{
solAssert(!!m_currentLValue, "LValue not retrieved.");
std::visit(
util::GenericVisitor{
[&](IRLValue::Storage const& _storage) {
appendCode() <<
m_utils.storageSetToZeroFunction(m_currentLValue->type) <<
"(" <<
_storage.slot <<
", " <<
_storage.offsetString() <<
")\n";
m_currentLValue.reset();
},
[&](auto const&) {
IRVariable zeroValue(m_context.newYulVariable(), m_currentLValue->type);
define(zeroValue) << m_utils.zeroValueFunction(m_currentLValue->type) << "()\n";
writeToLValue(*m_currentLValue, zeroValue);
m_currentLValue.reset();
}
},
m_currentLValue->kind
);
}
else if (resultType.category() == Type::Category::Integer)
{
solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!");
if (op == Token::Inc || op == Token::Dec)
{
solAssert(!!m_currentLValue, "LValue not retrieved.");
IRVariable modifiedValue(m_context.newYulVariable(), resultType);
IRVariable originalValue = readFromLValue(*m_currentLValue);
bool checked = m_context.arithmetic() == Arithmetic::Checked;
define(modifiedValue) <<
(op == Token::Inc ?
(checked ? m_utils.incrementCheckedFunction(resultType) : m_utils.incrementWrappingFunction(resultType)) :
(checked ? m_utils.decrementCheckedFunction(resultType) : m_utils.decrementWrappingFunction(resultType))
) <<
"(" <<
originalValue.name() <<
")\n";
writeToLValue(*m_currentLValue, modifiedValue);
m_currentLValue.reset();
define(_unaryOperation, _unaryOperation.isPrefixOperation() ? modifiedValue : originalValue);
}
else if (op == Token::BitNot)
appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
else if (op == Token::Add)
// According to SyntaxChecker...
solAssert(false, "Use of unary + is disallowed.");
else if (op == Token::Sub)
{
IntegerType const& intType = *dynamic_cast(&resultType);
define(_unaryOperation) << (
m_context.arithmetic() == Arithmetic::Checked ?
m_utils.negateNumberCheckedFunction(intType) :
m_utils.negateNumberWrappingFunction(intType)
) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n";
}
else
solUnimplemented("Unary operator not yet implemented");
}
else if (resultType.category() == Type::Category::FixedBytes)
{
solAssert(op == Token::BitNot, "Only bitwise negation is allowed for FixedBytes");
solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!");
appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
}
else if (resultType.category() == Type::Category::Bool)
{
solAssert(
op != Token::BitNot,
"Bitwise Negation can't be done on bool!"
);
appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
}
else
solUnimplemented("Unary operator not yet implemented");
return false;
}
bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
{
setLocation(_binOp);
solAssert(!!_binOp.annotation().commonType);
Type const* commonType = _binOp.annotation().commonType;
langutil::Token op = _binOp.getOperator();
if (op == Token::And || op == Token::Or)
{
// This can short-circuit!
appendAndOrOperatorCode(_binOp);
return false;
}
if (commonType->category() == Type::Category::RationalNumber)
{
define(_binOp) << toCompactHexWithPrefix(commonType->literalValue(nullptr)) << "\n";
return false; // skip sub-expressions
}
_binOp.leftExpression().accept(*this);
_binOp.rightExpression().accept(*this);
setLocation(_binOp);
if (TokenTraits::isCompareOp(op))
{
solAssert(commonType->isValueType());
bool isSigned = false;
if (auto type = dynamic_cast(commonType))
isSigned = type->isSigned();
string args = expressionAsCleanedType(_binOp.leftExpression(), *commonType);
args += ", " + expressionAsCleanedType(_binOp.rightExpression(), *commonType);
auto functionType = dynamic_cast(commonType);
solAssert(functionType ? (op == Token::Equal || op == Token::NotEqual) : true, "Invalid function pointer comparison!");
string expr;
if (functionType && functionType->kind() == FunctionType::Kind::External)
{
solUnimplementedAssert(functionType->sizeOnStack() == 2, "");
expr = m_utils.externalFunctionPointersEqualFunction() +
"(" +
IRVariable{_binOp.leftExpression()}.part("address").name() + "," +
IRVariable{_binOp.leftExpression()}.part("functionSelector").name() + "," +
IRVariable{_binOp.rightExpression()}.part("address").name() + "," +
IRVariable{_binOp.rightExpression()}.part("functionSelector").name() +
")";
if (op == Token::NotEqual)
expr = "iszero(" + expr + ")";
}
else if (op == Token::Equal)
expr = "eq(" + move(args) + ")";
else if (op == Token::NotEqual)
expr = "iszero(eq(" + move(args) + "))";
else if (op == Token::GreaterThanOrEqual)
expr = "iszero(" + string(isSigned ? "slt(" : "lt(") + move(args) + "))";
else if (op == Token::LessThanOrEqual)
expr = "iszero(" + string(isSigned ? "sgt(" : "gt(") + move(args) + "))";
else if (op == Token::GreaterThan)
expr = (isSigned ? "sgt(" : "gt(") + move(args) + ")";
else if (op == Token::LessThan)
expr = (isSigned ? "slt(" : "lt(") + move(args) + ")";
else
solAssert(false, "Unknown comparison operator.");
define(_binOp) << expr << "\n";
}
else if (op == Token::Exp)
{
IRVariable left = convert(_binOp.leftExpression(), *commonType);
IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
if (m_context.arithmetic() == Arithmetic::Wrapping)
define(_binOp) << m_utils.wrappingIntExpFunction(
dynamic_cast(left.type()),
dynamic_cast(right.type())
) << "(" << left.name() << ", " << right.name() << ")\n";
else if (auto rationalNumberType = dynamic_cast(_binOp.leftExpression().annotation().type))
{
solAssert(rationalNumberType->integerType(), "Invalid literal as the base for exponentiation.");
solAssert(dynamic_cast(commonType));
define(_binOp) << m_utils.overflowCheckedIntLiteralExpFunction(
*rationalNumberType,
dynamic_cast(right.type()),
dynamic_cast(*commonType)
) << "(" << right.name() << ")\n";
}
else
define(_binOp) << m_utils.overflowCheckedIntExpFunction(
dynamic_cast(left.type()),
dynamic_cast(right.type())
) << "(" << left.name() << ", " << right.name() << ")\n";
}
else if (TokenTraits::isShiftOp(op))
{
IRVariable left = convert(_binOp.leftExpression(), *commonType);
IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
define(_binOp) << shiftOperation(_binOp.getOperator(), left, right) << "\n";
}
else
{
string left = expressionAsType(_binOp.leftExpression(), *commonType);
string right = expressionAsType(_binOp.rightExpression(), *commonType);
define(_binOp) << binaryOperation(_binOp.getOperator(), *commonType, left, right) << "\n";
}
return false;
}
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
setLocation(_functionCall);
auto functionCallKind = *_functionCall.annotation().kind;
if (functionCallKind == FunctionCallKind::TypeConversion)
{
solAssert(
_functionCall.expression().annotation().type->category() == Type::Category::TypeType,
"Expected category to be TypeType"
);
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
define(_functionCall, *_functionCall.arguments().front());
return;
}
FunctionTypePointer functionType = nullptr;
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast(*type.actualType());
functionType = structType.constructorType();
}
else
functionType = dynamic_cast(_functionCall.expression().annotation().type);
TypePointers parameterTypes = functionType->parameterTypes();
vector> const& arguments = _functionCall.sortedArguments();
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast(*type.actualType());
define(_functionCall) << m_utils.allocateMemoryStructFunction(structType) << "()\n";
MemberList::MemberMap members = structType.nativeMembers(nullptr);
solAssert(members.size() == arguments.size(), "Struct parameter mismatch.");
for (size_t i = 0; i < arguments.size(); i++)
{
IRVariable converted = convert(*arguments[i], *parameterTypes[i]);
appendCode() <<
m_utils.writeToMemoryFunction(*functionType->parameterTypes()[i]) <<
"(add(" <<
IRVariable(_functionCall).part("mpos").name() <<
", " <<
structType.memoryOffsetOfMember(members[i].name) <<
"), " <<
converted.commaSeparatedList() <<
")\n";
}
return;
}
switch (functionType->kind())
{
case FunctionType::Kind::Declaration:
solAssert(false, "Attempted to generate code for calling a function definition.");
break;
case FunctionType::Kind::Internal:
{
FunctionDefinition const* functionDef = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract());
solAssert(!functionType->takesArbitraryParameters());
vector args;
if (functionType->bound())
args += IRVariable(_functionCall.expression()).part("self").stackSlots();
for (size_t i = 0; i < arguments.size(); ++i)
args += convert(*arguments[i], *parameterTypes[i]).stackSlots();
if (functionDef)
{
solAssert(functionDef->isImplemented());
define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(*functionDef) <<
"(" <<
joinHumanReadable(args) <<
")\n";
}
else
{
YulArity arity = YulArity::fromType(*functionType);
m_context.internalFunctionCalledThroughDispatch(arity);
define(_functionCall) <<
IRNames::internalDispatch(arity) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
")\n";
}
break;
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
appendExternalFunctionCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
appendBareCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
case FunctionType::Kind::Event:
{
auto const& event = dynamic_cast(functionType->declaration());
TypePointers paramTypes = functionType->parameterTypes();
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
vector indexedArgs;
vector nonIndexedArgs;
TypePointers nonIndexedArgTypes;
TypePointers nonIndexedParamTypes;
if (!event.isAnonymous())
define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
formatNumber(u256(h256::Arith(keccak256(functionType->externalSignature())))) << "\n";
for (size_t i = 0; i < event.parameters().size(); ++i)
{
Expression const& arg = *arguments[i];
if (event.parameters()[i]->isIndexed())
{
string value;
if (auto const& referenceType = dynamic_cast(paramTypes[i]))
define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
m_utils.packedHashFunction({arg.annotation().type}, {referenceType}) <<
"(" <<
IRVariable(arg).commaSeparatedList() <<
")\n";
else if (auto functionType = dynamic_cast(paramTypes[i]))
{
solAssert(
IRVariable(arg).type() == *functionType &&
functionType->kind() == FunctionType::Kind::External &&
!functionType->bound(),
""
);
define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::fixedBytes(32))) <<
m_utils.combineExternalFunctionIdFunction() <<
"(" <<
IRVariable(arg).commaSeparatedList() <<
")\n";
}
else
{
solAssert(parameterTypes[i]->sizeOnStack() == 1, "");
indexedArgs.emplace_back(convertAndCleanup(arg, *parameterTypes[i]));
}
}
else
{
nonIndexedArgs += IRVariable(arg).stackSlots();
nonIndexedArgTypes.push_back(arg.annotation().type);
nonIndexedParamTypes.push_back(paramTypes[i]);
}
}
solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
Whiskers templ(R"({
let := ()
let := ( )
(, sub(, ) )
})");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
templ("nonIndexedArgs", joinHumanReadablePrefixed(nonIndexedArgs));
templ("log", "log" + to_string(indexedArgs.size()));
templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs | ranges::views::transform([&](auto const& _arg) {
return _arg.commaSeparatedList();
})));
appendCode() << templ.render();
break;
}
case FunctionType::Kind::Error:
{
ErrorDefinition const* error = dynamic_cast(ASTNode::referencedDeclaration(_functionCall.expression()));
solAssert(error);
revertWithError(
error->functionType(true)->externalSignature(),
error->functionType(true)->parameterTypes(),
_functionCall.sortedArguments()
);
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
solAssert(arguments.size() == 1);
FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap)
solAssert(
type(*arguments.at(0)).isImplicitlyConvertibleTo(
dynamic_cast(type(_functionCall)).underlyingType()
),
""
);
else
solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType);
define(_functionCall, *arguments.at(0));
break;
}
case FunctionType::Kind::Assert:
case FunctionType::Kind::Require:
{
solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
Type const* messageArgumentType =
arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip ?
arguments[1]->annotation().type :
nullptr;
string requireOrAssertFunction = m_utils.requireOrAssertFunction(
functionType->kind() == FunctionType::Kind::Assert,
messageArgumentType
);
appendCode() << move(requireOrAssertFunction) << "(" << IRVariable(*arguments[0]).name();
if (messageArgumentType && messageArgumentType->sizeOnStack() > 0)
appendCode() << ", " << IRVariable(*arguments[1]).commaSeparatedList();
appendCode() << ")\n";
break;
}
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature:
{
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(functionType->padArguments() != isPacked);
bool const hasSelectorOrSignature =
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes;
TypePointers targetTypes;
vector argumentVars;
string selector;
vector> argumentsOfEncodeFunction;
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
solAssert(arguments.size() == 2);
// Account for tuples with one component which become that component
if (type(*arguments[1]).category() == Type::Category::Tuple)
{
auto const& tupleExpression = dynamic_cast(*arguments[1]);
for (auto component: tupleExpression.components())
argumentsOfEncodeFunction.push_back(component);
}
else
argumentsOfEncodeFunction.push_back(arguments[1]);
}
else
for (size_t i = 0; i < arguments.size(); ++i)
{
// ignore selector
if (hasSelectorOrSignature && i == 0)
continue;
argumentsOfEncodeFunction.push_back(arguments[i]);
}
for (auto const& argument: argumentsOfEncodeFunction)
{
argumentTypes.emplace_back(&type(*argument));
argumentVars += IRVariable(*argument).stackSlots();
}
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
auto encodedFunctionType = dynamic_cast(arguments.front()->annotation().type);
solAssert(encodedFunctionType);
encodedFunctionType = encodedFunctionType->asExternallyCallableFunction(false);
solAssert(encodedFunctionType);
targetTypes = encodedFunctionType->parameterTypes();
}
else
for (auto const& argument: argumentsOfEncodeFunction)
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
auto const& selectorType = dynamic_cast(type(*arguments.front()));
if (selectorType.kind() == FunctionType::Kind::Declaration)
{
solAssert(selectorType.hasDeclaration());
selector = formatNumber(selectorType.externalIdentifier() << (256 - 32));
}
else
{
selector = convert(
IRVariable(*arguments[0]).part("functionSelector"),
*TypeProvider::fixedBytes(4)
).name();
}
}
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
{
// hash the signature
Type const& selectorType = type(*arguments.front());
if (auto const* stringType = dynamic_cast(&selectorType))
selector = formatNumber(util::selectorFromSignature(stringType->value()));
else
{
// Used to reset the free memory pointer later.
// TODO This is an abuse of the `allocateUnbounded` function.
// We might want to introduce a new set of memory handling functions here
// a la "setMemoryCheckPoint" and "freeUntilCheckPoint".
string freeMemoryPre = m_context.newYulVariable();
appendCode() << "let " << freeMemoryPre << " := " << m_utils.allocateUnboundedFunction() << "()\n";
IRVariable array = convert(*arguments[0], *TypeProvider::bytesMemory());
IRVariable hashVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(32));
string dataAreaFunction = m_utils.arrayDataAreaFunction(*TypeProvider::bytesMemory());
string arrayLengthFunction = m_utils.arrayLengthFunction(*TypeProvider::bytesMemory());
define(hashVariable) <<
"keccak256(" <<
(dataAreaFunction + "(" + array.commaSeparatedList() + ")") <<
", " <<
(arrayLengthFunction + "(" + array.commaSeparatedList() +")") <<
")\n";
IRVariable selectorVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(4));
define(selectorVariable, hashVariable);
selector = selectorVariable.name();
appendCode() << m_utils.finalizeAllocationFunction() << "(" << freeMemoryPre << ", 0)\n";
}
}
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector)
selector = convert(*arguments.front(), *TypeProvider::fixedBytes(4)).name();
Whiskers templ(R"(
let := ()
let := add(, 0x20)
+selector>
mstore(, )
:= add(, 4)
+selector>
let := ()
mstore(, sub(, add(, 0x20)))
(, sub(, ))
)");
templ("data", IRVariable(_functionCall).part("mpos").name());
templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
templ("memPtr", m_context.newYulVariable());
templ("mend", m_context.newYulVariable());
templ("selector", selector);
templ("encode",
isPacked ?
m_context.abiFunctions().tupleEncoderPacked(argumentTypes, targetTypes) :
m_context.abiFunctions().tupleEncoder(argumentTypes, targetTypes, false)
);
templ("arguments", joinHumanReadablePrefixed(argumentVars));
templ("finalizeAllocation", m_utils.finalizeAllocationFunction());
appendCode() << templ.render();
break;
}
case FunctionType::Kind::ABIDecode:
{
Whiskers templ(R"(
+retVars>let := +retVars> (, add(, ))
)");
Type const* firstArgType = arguments.front()->annotation().type;
TypePointers targetTypes;
if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type))
targetTypes = targetTupleType->components();
else
targetTypes = TypePointers{_functionCall.annotation().type};
if (
auto referenceType = dynamic_cast(firstArgType);
referenceType && referenceType->dataStoredIn(DataLocation::CallData)
)
{
solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()));
IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false));
templ("offset", var.part("offset").name());
templ("length", var.part("length").name());
}
else
{
IRVariable var = convert(*arguments[0], *TypeProvider::bytesMemory());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, true));
templ("offset", "add(" + var.part("mpos").name() + ", 32)");
templ("length",
m_utils.arrayLengthFunction(*TypeProvider::bytesMemory()) + "(" + var.part("mpos").name() + ")"
);
}
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
appendCode() << templ.render();
break;
}
case FunctionType::Kind::Revert:
{
solAssert(arguments.size() == parameterTypes.size());
solAssert(arguments.size() <= 1);
solAssert(
arguments.empty() ||
arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()),
"");
if (m_context.revertStrings() == RevertStrings::Strip || arguments.empty())
appendCode() << "revert(0, 0)\n";
else
revertWithError(
"Error(string)",
{TypeProvider::stringMemory()},
{arguments.front()}
);
break;
}
// Array creation using new
case FunctionType::Kind::ObjectCreation:
{
ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type);
solAssert(arguments.size() == 1);
IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) <<
m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
"(" <<
value.commaSeparatedList() <<
")\n";
break;
}
case FunctionType::Kind::KECCAK256:
{
solAssert(arguments.size() == 1);
ArrayType const* arrayType = TypeProvider::bytesMemory();
if (auto const* stringLiteral = dynamic_cast(arguments.front()->annotation().type))
{
// Optimization: Compute keccak256 on string literals at compile-time.
define(_functionCall) <<
("0x" + keccak256(stringLiteral->value()).hex()) <<
"\n";
}
else
{
auto array = convert(*arguments[0], *arrayType);
string dataAreaFunction = m_utils.arrayDataAreaFunction(*arrayType);
string arrayLengthFunction = m_utils.arrayLengthFunction(*arrayType);
define(_functionCall) <<
"keccak256(" <<
(dataAreaFunction + "(" + array.commaSeparatedList() + ")") <<
", " <<
(arrayLengthFunction + "(" + array.commaSeparatedList() +")") <<
")\n";
}
break;
}
case FunctionType::Kind::ArrayPop:
{
solAssert(functionType->bound());
solAssert(functionType->parameterTypes().empty());
ArrayType const* arrayType = dynamic_cast(functionType->selfType());
solAssert(arrayType);
define(_functionCall) <<
m_utils.storageArrayPopFunction(*arrayType) <<
"(" <<
IRVariable(_functionCall.expression()).commaSeparatedList() <<
")\n";
break;
}
case FunctionType::Kind::ArrayPush:
{
ArrayType const* arrayType = dynamic_cast(functionType->selfType());
solAssert(arrayType);
if (arguments.empty())
{
auto slotName = m_context.newYulVariable();
auto offsetName = m_context.newYulVariable();
appendCode() << "let " << slotName << ", " << offsetName << " := " <<
m_utils.storageArrayPushZeroFunction(*arrayType) <<
"(" << IRVariable(_functionCall.expression()).commaSeparatedList() << ")\n";
setLValue(_functionCall, IRLValue{
*arrayType->baseType(),
IRLValue::Storage{
slotName,
offsetName,
}
});
}
else
{
IRVariable argument =
arrayType->baseType()->isValueType() ?
convert(*arguments.front(), *arrayType->baseType()) :
*arguments.front();
appendCode() <<
m_utils.storageArrayPushFunction(*arrayType, &argument.type()) <<
"(" <<
IRVariable(_functionCall.expression()).commaSeparatedList() <<
(argument.stackSlots().empty() ? "" : (", " + argument.commaSeparatedList())) <<
")\n";
}
break;
}
case FunctionType::Kind::StringConcat:
case FunctionType::Kind::BytesConcat:
{
TypePointers argumentTypes;
vector argumentVars;
for (ASTPointer const& argument: arguments)
{
argumentTypes.emplace_back(&type(*argument));
argumentVars += IRVariable(*argument).stackSlots();
}
define(IRVariable(_functionCall)) <<
m_utils.bytesOrStringConcatFunction(argumentTypes, functionType->kind()) <<
"(" <<
joinHumanReadable(argumentVars) <<
")\n";
break;
}
case FunctionType::Kind::MetaType:
{
break;
}
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
{
static map functions = {
{FunctionType::Kind::AddMod, "addmod"},
{FunctionType::Kind::MulMod, "mulmod"},
};
solAssert(functions.find(functionType->kind()) != functions.end());
solAssert(arguments.size() == 3 && parameterTypes.size() == 3);
IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2]));
define(modulus, *arguments[2]);
Whiskers templ("if iszero() { () }\n");
templ("modulus", modulus.name());
templ("panic", m_utils.panicFunction(PanicCode::DivisionByZero));
appendCode() << templ.render();
string args;
for (size_t i = 0; i < 2; ++i)
args += expressionAsType(*arguments[i], *(parameterTypes[i])) + ", ";
args += modulus.name();
define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
break;
}
case FunctionType::Kind::GasLeft:
case FunctionType::Kind::Selfdestruct:
case FunctionType::Kind::BlockHash:
{
static map functions = {
{FunctionType::Kind::GasLeft, "gas"},
{FunctionType::Kind::Selfdestruct, "selfdestruct"},
{FunctionType::Kind::BlockHash, "blockhash"},
};
solAssert(functions.find(functionType->kind()) != functions.end());
string args;
for (size_t i = 0; i < arguments.size(); ++i)
args += (args.empty() ? "" : ", ") + expressionAsType(*arguments[i], *(parameterTypes[i]));
define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
break;
}
case FunctionType::Kind::Creation:
{
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
solAssert(
functionType->returnParameterTypes().size() == 1,
"Constructor should return only one type"
);
TypePointers argumentTypes;
vector constructorParams;
for (ASTPointer const& arg: arguments)
{
argumentTypes.push_back(arg->annotation().type);
constructorParams += IRVariable{*arg}.stackSlots();
}
ContractDefinition const* contract =
&dynamic_cast(*functionType->returnParameterTypes().front()).contractDefinition();
m_context.subObjectsCreated().insert(contract);
Whiskers t(R"(let := ()
let := add(, datasize("