Use IRVariable's in IR code generation and implement tuples.

This commit is contained in:
Daniel Kirchner 2020-02-06 14:05:25 +01:00
parent 6abe0a50b1
commit 3c9f18b749
20 changed files with 855 additions and 649 deletions

View File

@ -79,8 +79,9 @@ set(sources
codegen/ir/IRGeneratorForStatements.h
codegen/ir/IRGenerationContext.cpp
codegen/ir/IRGenerationContext.h
codegen/ir/IRLValue.cpp
codegen/ir/IRLValue.h
codegen/ir/IRVariable.cpp
codegen/ir/IRVariable.h
formal/BMC.cpp
formal/BMC.h
formal/CHC.cpp

View File

@ -151,7 +151,7 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
void Type::clearCache() const
{
m_members.clear();
m_stackSlots.reset();
m_stackItems.reset();
m_stackSize.reset();
}
@ -1703,14 +1703,22 @@ u256 ArrayType::storageSize() const
return max<u256>(1, u256(size));
}
vector<tuple<string, TypePointer>> ArrayType::makeStackSlots() const
vector<tuple<string, TypePointer>> ArrayType::makeStackItems() const
{
if (m_location == DataLocation::CallData && isDynamicallySized())
switch (m_location)
{
case DataLocation::CallData:
if (isDynamicallySized())
return {std::make_tuple("offset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
else
// storage slot or memory offset
return {std::make_tuple("offset", TypeProvider::uint256())};
case DataLocation::Memory:
return {std::make_tuple("mpos", TypeProvider::uint256())};
case DataLocation::Storage:
// byte offset inside storage value is omitted
return {std::make_tuple(string(), nullptr)};
return {std::make_tuple("slot", TypeProvider::uint256())};
}
solAssert(false, "");
}
string ArrayType::toString(bool _short) const
@ -1892,7 +1900,7 @@ string ArraySliceType::toString(bool _short) const
return m_arrayType.toString(_short) + " slice";
}
std::vector<std::tuple<std::string, TypePointer>> ArraySliceType::makeStackSlots() const
std::vector<std::tuple<std::string, TypePointer>> ArraySliceType::makeStackItems() const
{
return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}};
}
@ -1995,12 +2003,12 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<tuple<string, TypePointer>> ContractType::makeStackSlots() const
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
return {};
else
return {make_tuple("address", isPayable() ? TypeProvider::payableAddress() :TypeProvider::address())};
return {make_tuple("address", isPayable() ? TypeProvider::payableAddress() : TypeProvider::address())};
}
void StructType::clearCache() const
@ -2436,7 +2444,7 @@ u256 TupleType::storageSize() const
solAssert(false, "Storage size of non-storable tuple type requested.");
}
vector<tuple<string, TypePointer>> TupleType::makeStackSlots() const
vector<tuple<string, TypePointer>> TupleType::makeStackItems() const
{
vector<tuple<string, TypePointer>> slots;
unsigned i = 1;
@ -2902,7 +2910,7 @@ unsigned FunctionType::storageBytes() const
solAssert(false, "Storage size of non-storable function type requested.");
}
vector<tuple<string, TypePointer>> FunctionType::makeStackSlots() const
vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{
vector<tuple<string, TypePointer>> slots;
Kind kind = m_kind;
@ -2927,12 +2935,12 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackSlots() const
slots = {make_tuple("address", TypeProvider::address())};
break;
case Kind::Internal:
slots = {make_tuple(string(), nullptr)};
slots = {make_tuple("functionIdentifier", TypeProvider::uint256())};
break;
case Kind::ArrayPush:
case Kind::ArrayPop:
case Kind::ByteArrayPush:
slots = {make_tuple(string(), nullptr)};
slots = {make_tuple("slot", TypeProvider::uint256())};
break;
default:
break;
@ -2945,7 +2953,7 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackSlots() const
if (m_saltSet)
slots.emplace_back("salt", TypeProvider::uint256());
if (bound())
for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackSlots())
for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems())
slots.emplace_back("self_" + boundName, boundType);
return slots;
}
@ -3441,7 +3449,7 @@ u256 TypeType::storageSize() const
solAssert(false, "Storage size of non-storable type type requested.");
}
vector<tuple<string, TypePointer>> TypeType::makeStackSlots() const
vector<tuple<string, TypePointer>> TypeType::makeStackItems() const
{
if (auto contractType = dynamic_cast<ContractType const*>(m_actualType))
if (contractType->contractDefinition().isLibrary())

View File

@ -259,18 +259,25 @@ public:
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
std::vector<std::tuple<std::string, TypePointer>> const& stackSlots() const
/// @returns a list of named and typed stack items that determine the layout of this type on the stack.
/// A stack item either has an empty name and type ``nullptr`` referring to a single stack slot, or
/// has a non-empty name and a valid type referring to the stack layout of that type.
/// The complete layout of a type on the stack can be obtained from its stack items recursively as follows:
/// - Each unnamed stack item is untyped (its type is ``nullptr``) and contributes exactly one stack slot.
/// - Each named stack item is typed and contributes the stack slots given by the stack items of its type.
std::vector<std::tuple<std::string, TypePointer>> const& stackItems() const
{
if (!m_stackSlots)
m_stackSlots = makeStackSlots();
return *m_stackSlots;
if (!m_stackItems)
m_stackItems = makeStackItems();
return *m_stackItems;
}
/// Total number of stack slots occupied by this type. This is the sum of ``sizeOnStack`` of all ``stackItems()``.
unsigned sizeOnStack() const
{
if (!m_stackSize)
{
size_t sizeOnStack = 0;
for (auto const& slot: stackSlots())
for (auto const& slot: stackItems())
if (std::get<1>(slot))
sizeOnStack += std::get<1>(slot)->sizeOnStack();
else
@ -355,7 +362,9 @@ protected:
{
return MemberList::MemberMap();
}
virtual std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const
/// Generates the stack items to be returned by ``stackItems()``. Defaults
/// to exactly one unnamed and untyped stack item referring to a single stack slot.
virtual std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const
{
return {std::make_tuple(std::string(), nullptr)};
}
@ -363,7 +372,7 @@ protected:
/// List of member types (parameterised by scape), will be lazy-initialized.
mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members;
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackSlots;
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackItems;
mutable std::optional<size_t> m_stackSize;
};
@ -597,7 +606,7 @@ public:
std::string const& value() const { return m_value; }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override { return {}; }
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:
std::string m_value;
};
@ -783,7 +792,7 @@ public:
void clearCache() const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
/// String is interpreted as a subtype of Bytes.
enum class ArrayKind { Ordinary, Bytes, String };
@ -824,7 +833,7 @@ public:
std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
ArrayType const& m_arrayType;
};
@ -885,7 +894,7 @@ public:
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
ContractDefinition const& m_contract;
/// If true, this is a special "super" type of m_contract containing only members that m_contract inherited
@ -1026,7 +1035,7 @@ public:
std::vector<TypePointer> const& components() const { return m_components; }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
std::vector<TypePointer> const m_components;
};
@ -1282,7 +1291,7 @@ public:
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
static TypePointers parseElementaryTypeVector(strings const& _types);
@ -1358,7 +1367,7 @@ public:
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override;
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
TypePointer m_actualType;
};
@ -1384,7 +1393,7 @@ public:
std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override { return {}; }
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:
TypePointers m_parameterTypes;
};
@ -1412,7 +1421,7 @@ public:
std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override { return {}; }
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:
SourceUnit const& m_sourceUnit;
};
@ -1456,7 +1465,7 @@ public:
TypePointer typeArgument() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackSlots() const override { return {}; }
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:
Kind m_kind;
/// Contract type used for contract metadata magic.

View File

@ -1791,6 +1791,44 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
"_to_" +
_to.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
if (
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
)
{
size_t sourceStackSize = 0;
size_t destStackSize = 0;
std::string conversions;
for (size_t i = 0; i < fromTuple->components().size(); ++i)
{
auto fromComponent = fromTuple->components()[i];
auto toComponent = toTuple->components()[i];
solAssert(fromComponent, "");
if (toComponent)
{
conversions +=
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
" := " +
conversionFunction(*fromComponent, *toComponent) +
"(" +
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
")\n";
destStackSize += toComponent->sizeOnStack();
}
sourceStackSize += fromComponent->sizeOnStack();
}
return Whiskers(R"(
function <functionName>(<values>) -> <converted> {
<conversions>
}
)")
("functionName", functionName)
("values", suffixedVariableNameList("value", 0, sourceStackSize))
("converted", suffixedVariableNameList("converted", 0, destStackSize))
("conversions", conversions)
.render();
}
solUnimplementedAssert(
_from.category() == Type::Category::StringLiteral,
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."

View File

@ -31,23 +31,22 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
{
solUnimplementedAssert(
_varDecl.annotation().type->sizeOnStack() == 1,
"Multi-slot types not yet implemented."
auto const& [it, didInsert] = m_localVariables.emplace(
std::make_pair(&_varDecl, IRVariable{_varDecl})
);
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
solAssert(didInsert, "Local variable added multiple times.");
return it->second;
}
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const& _varDecl)
{
solAssert(
m_localVariables.count(&_varDecl),
"Unknown variable: " + _varDecl.name()
);
return m_localVariables[&_varDecl];
return m_localVariables.at(&_varDecl);
}
void IRGenerationContext::addStateVariable(
@ -98,23 +97,6 @@ string IRGenerationContext::newYulVariable()
return "_" + to_string(++m_varCounter);
}
string IRGenerationContext::variable(Expression const& _expression)
{
unsigned size = _expression.annotation().type->sizeOnStack();
string var = "expr_" + to_string(_expression.id());
if (size == 1)
return var;
else
return suffixedVariableNameList(move(var) + "_", 1, 1 + size);
}
string IRGenerationContext::variablePart(Expression const& _expression, string const& _part)
{
size_t numVars = _expression.annotation().type->sizeOnStack();
solAssert(numVars > 1, "");
return "expr_" + to_string(_expression.id()) + "_" + _part;
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);

View File

@ -20,6 +20,7 @@
#pragma once
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
@ -68,9 +69,9 @@ public:
}
std::string addLocalVariable(VariableDeclaration const& _varDecl);
IRVariable const& addLocalVariable(VariableDeclaration const& _varDecl);
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
std::string localVariableName(VariableDeclaration const& _varDecl);
IRVariable const& localVariable(VariableDeclaration const& _varDecl);
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
@ -85,11 +86,6 @@ public:
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable();
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
/// @returns the sub-variable of a multi-variable expression.
std::string variablePart(Expression const& _expression, std::string const& _part);
std::string internalDispatch(size_t _in, size_t _out);
@ -109,7 +105,7 @@ private:
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables;
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;

View File

@ -139,11 +139,11 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
t("functionName", functionName);
string params;
for (auto const& varDecl: _function.parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
for (auto const& varDecl: _function.returnParameters())
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("body", generate(_function.body()));
return t.render();

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRVariable.h>
namespace solidity::frontend
{
@ -75,12 +76,25 @@ private:
std::string fetchFreeMem() const;
/// Generates the required conversion code and @returns an IRVariable referring to the value of @a _variable
/// converted to type @a _to.
IRVariable convert(IRVariable const& _variable, Type const& _to);
/// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
/// Defines only one of many variables corresponding to an expression.
std::ostream& defineExpressionPart(Expression const& _expression, std::string const& _part);
/// @returns an output stream that can be used to define @a _var using a function call or
/// single stack slot expression.
std::ostream& define(IRVariable const& _var);
/// Defines @a _var using the value of @a _value while performing type conversions, if required.
void define(IRVariable const& _var, IRVariable const& _value) { declareAssign(_var, _value, true); }
/// Assigns @a _var to the value of @a _value while performing type conversions, if required.
void assign(IRVariable const& _var, IRVariable const& _value) { declareAssign(_var, _value, false); }
/// Declares variable @a _var.
void declare(IRVariable const& _var);
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
@ -93,7 +107,14 @@ private:
std::string const& _right
);
void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue);
/// Assigns the value of @a _value to the lvalue @a _lvalue.
void writeToLValue(IRLValue const& _lvalue, IRVariable const& _value);
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
IRVariable readFromLValue(IRLValue const& _lvalue);
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (lValueRequested). Otherwise
/// defines the expression @a _expression by reading the value from @a _lvalue.
void setLValue(Expression const& _expression, IRLValue _lvalue);
void generateLoop(
Statement const& _body,
Expression const* _conditionExpression,
@ -107,7 +128,7 @@ private:
std::ostringstream m_code;
IRGenerationContext& m_context;
YulUtilFunctions& m_utils;
std::unique_ptr<IRLValue> m_currentLValue;
std::optional<IRLValue> m_currentLValue;
};
}

View File

@ -1,228 +0,0 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* Generator for code that handles LValues.
*/
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolutil/Whiskers.h>
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
IRLocalVariable::IRLocalVariable(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_context.utils(), _varDecl.annotation().type),
m_variableName(_context.localVariableName(_varDecl))
{
}
string IRLocalVariable::storeValue(string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Storing different types - not necessarily a problem.");
return m_variableName + " := " + _value + "\n";
}
string IRLocalVariable::setToZero() const
{
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
}
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRStorageItem(
_context.utils(),
*_varDecl.annotation().type,
_context.storageLocationOfVariable(_varDecl)
)
{ }
IRStorageItem::IRStorageItem(
YulUtilFunctions _utils,
Type const& _type,
std::pair<u256, unsigned> slot_offset
):
IRLValue(std::move(_utils), &_type),
m_slot(util::toCompactHexWithPrefix(slot_offset.first)),
m_offset(slot_offset.second)
{
}
IRStorageItem::IRStorageItem(
YulUtilFunctions _utils,
string _slot,
boost::variant<string, unsigned> _offset,
Type const& _type
):
IRLValue(std::move(_utils), &_type),
m_slot(std::move(_slot)),
m_offset(std::move(_offset))
{
solAssert(!m_offset.empty(), "");
solAssert(!m_slot.empty(), "");
}
string IRStorageItem::retrieveValue() const
{
if (!m_type->isValueType())
return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
if (m_offset.type() == typeid(string))
return
m_utils.readFromStorageDynamic(*m_type, false) +
"(" +
m_slot +
", " +
boost::get<string>(m_offset) +
")";
else if (m_offset.type() == typeid(unsigned))
return
m_utils.readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
"(" +
m_slot +
")";
solAssert(false, "");
}
string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{
if (m_type->isValueType())
solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
std::optional<unsigned> offset;
if (m_offset.type() == typeid(unsigned))
offset = get<unsigned>(m_offset);
return
m_utils.updateStorageValueFunction(*m_type, offset) +
"(" +
m_slot +
(m_offset.type() == typeid(string) ?
(", " + get<string>(m_offset)) :
""
) +
", " +
_value +
")\n";
}
string IRStorageItem::setToZero() const
{
return
m_utils.storageSetToZeroFunction(*m_type) +
"(" +
m_slot +
", " +
(
m_offset.type() == typeid(unsigned) ?
to_string(get<unsigned>(m_offset)) :
get<string>(m_offset)
) +
")\n";
}
IRMemoryItem::IRMemoryItem(
YulUtilFunctions _utils,
std::string _address,
bool _byteArrayElement,
Type const& _type
):
IRLValue(std::move(_utils), &_type),
m_address(move(_address)),
m_byteArrayElement(_byteArrayElement)
{ }
string IRMemoryItem::retrieveValue() const
{
if (m_byteArrayElement)
return m_utils.cleanupFunction(*m_type) +
"(mload(" +
m_address +
"))";
if (m_type->isValueType())
return m_utils.readFromMemory(*m_type) +
"(" +
m_address +
")";
else
return "mload(" + m_address + ")";
}
string IRMemoryItem::storeValue(string const& _value, Type const& _type) const
{
if (!m_type->isValueType())
{
solUnimplementedAssert(_type == *m_type, "Conversion not implemented for assignment to memory.");
solAssert(m_type->sizeOnStack() == 1, "");
solAssert(dynamic_cast<ReferenceType const*>(m_type), "");
return "mstore(" + m_address + ", " + _value + ")\n";
}
solAssert(_type.isValueType(), "");
string prepared = _value;
// Exists to see if this case ever happens
solAssert(_type == *m_type, "");
if (_type != *m_type)
prepared =
m_utils.conversionFunction(_type, *m_type) +
"(" +
_value +
")";
else
prepared =
m_utils.cleanupFunction(*m_type) +
"(" +
_value +
")";
if (m_byteArrayElement)
{
solAssert(*m_type == *TypeProvider::byte(), "");
return "mstore8(" + m_address + ", byte(0, " + prepared + "))\n";
}
else
return m_utils.writeToMemoryFunction(*m_type) +
"(" +
m_address +
", " +
prepared +
")\n";
}
string IRMemoryItem::setToZero() const
{
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
}

View File

@ -15,115 +15,51 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Generator for code that handles LValues.
* Classes that store locations of lvalues.
*/
#pragma once
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolutil/Common.h>
#include <string>
#include <ostream>
#include <boost/variant.hpp>
#include <libsolidity/codegen/ir/IRVariable.h>
#include <variant>
namespace solidity::frontend
{
class VariableDeclaration;
class IRGenerationContext;
class Type;
class ArrayType;
/**
* Abstract class used to retrieve, delete and store data in LValues.
*/
class IRLValue
struct IRLValue
{
protected:
explicit IRLValue(YulUtilFunctions _utils, Type const* _type = nullptr):
m_utils(std::move(_utils)),
m_type(_type)
{}
public:
virtual ~IRLValue() = default;
/// @returns an expression to retrieve the value of the lvalue.
virtual std::string retrieveValue() const = 0;
/// Returns code that stores the value of @a _value (should be an identifier)
/// of type @a _type in the lvalue. Might perform type conversion.
virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0;
/// Returns code that will reset the stored value to zero
virtual std::string setToZero() const = 0;
protected:
YulUtilFunctions mutable m_utils;
Type const* m_type;
};
class IRLocalVariable: public IRLValue
{
public:
IRLocalVariable(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
std::string retrieveValue() const override { return m_variableName; }
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
std::string m_variableName;
};
class IRStorageItem: public IRLValue
{
public:
IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
IRStorageItem(
YulUtilFunctions _utils,
std::string _slot,
boost::variant<std::string, unsigned> _offset,
Type const& _type
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
IRStorageItem(
YulUtilFunctions _utils,
Type const& _type,
std::pair<u256, unsigned> slot_offset
);
std::string const m_slot;
Type const& type;
struct Stack
{
IRVariable variable;
};
struct Storage
{
std::string const slot;
/// unsigned: Used when the offset is known at compile time, uses optimized
/// functions
/// string: Used when the offset is determined at run time
boost::variant<std::string, unsigned> const m_offset;
};
class IRMemoryItem: public IRLValue
{
public:
IRMemoryItem(
YulUtilFunctions _utils,
std::string _address,
bool _byteArrayElement,
Type const& _type
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
std::string const m_address;
bool m_byteArrayElement;
std::variant<std::string, unsigned> const offset;
std::string offsetString() const
{
if (std::holds_alternative<unsigned>(offset))
return std::to_string(std::get<unsigned>(offset));
else
return std::get<std::string>(offset);
}
};
struct Memory
{
std::string const address;
bool byteArrayElement = false;
};
struct Tuple
{
std::vector<std::optional<IRLValue>> components;
};
std::variant<Stack, Storage, Memory, Tuple> kind;
};
}

View File

@ -0,0 +1,111 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/ast/AST.h>
#include <boost/range/adaptor/transformed.hpp>
#include <libsolutil/StringUtils.h>
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
using namespace solidity::util;
IRVariable::IRVariable(std::string _baseName, Type const& _type):
m_baseName(std::move(_baseName)), m_type(_type)
{
}
IRVariable::IRVariable(VariableDeclaration const& _declaration):
IRVariable(
"vloc_" + _declaration.name() + '_' + std::to_string(_declaration.id()),
*_declaration.annotation().type
)
{
solAssert(!_declaration.isStateVariable(), "");
}
IRVariable::IRVariable(Expression const& _expression):
IRVariable(
"expr_" + to_string(_expression.id()),
*_expression.annotation().type
)
{
}
IRVariable IRVariable::part(string const& _name) const
{
for (auto const& [itemName, itemType]: m_type.stackItems())
if (itemName == _name)
{
solAssert(itemName.empty() || itemType, "");
return IRVariable{suffixedName(itemName), itemType ? *itemType : m_type};
}
solAssert(false, "Invalid stack item name.");
}
vector<string> IRVariable::stackSlots() const
{
vector<string> result;
for (auto const& [itemName, itemType]: m_type.stackItems())
if (itemType)
{
solAssert(!itemName.empty(), "");
solAssert(m_type != *itemType, "");
result += IRVariable{suffixedName(itemName), *itemType}.stackSlots();
}
else
{
solAssert(itemName.empty(), "");
result.emplace_back(m_baseName);
}
return result;
}
string IRVariable::commaSeparatedList() const
{
return joinHumanReadable(stackSlots());
}
string IRVariable::commaSeparatedListPrefixed() const
{
return joinHumanReadablePrefixed(stackSlots());
}
string IRVariable::name() const
{
solAssert(m_type.sizeOnStack() == 1, "");
auto const& [itemName, type] = m_type.stackItems().front();
solAssert(!type, "");
return suffixedName(itemName);
}
IRVariable IRVariable::tupleComponent(size_t _i) const
{
solAssert(
m_type.category() == Type::Category::Tuple,
"Requested tuple component of non-tuple IR variable."
);
return part("component_" + std::to_string(_i + 1));
}
string IRVariable::suffixedName(string const& _suffix) const
{
if (_suffix.empty())
return m_baseName;
else
return m_baseName + '_' + _suffix;
}

View File

@ -0,0 +1,85 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <optional>
#include <string>
#include <vector>
namespace solidity::frontend
{
class VariableDeclaration;
class Type;
class Expression;
/**
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
* of a specific S
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
* type or a memory reference type will contain a single unnamed part containing an offset. An IRVariable referring to
* a value type will contain a single unnamed part containing the value, an IRVariable referring to a tuple will
* have the typed tuple components as parts.
*/
class IRVariable
{
public:
/// IR variable with explicit base name @a _baseName and type @a _type.
IRVariable(std::string _baseName, Type const& _type);
/// IR variable referring to the declaration @a _decl.
explicit IRVariable(VariableDeclaration const& _decl);
/// IR variable referring to the expression @a _expr.
/// Intentionally not defined as explicit to allow defining IRVariables for expressions directly via implicit conversions.
IRVariable(Expression const& _expression);
/// @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 const& type() const { return m_type; }
/// @returns an IRVariable referring to the stack component @a _slot of the variable.
/// @a _slot must be among the stack slots in ``m_type.stackItems()``.
/// The returned IRVariable is itself typed with the type of the stack slot as defined
/// in ``m_type.stackItems()`` and may again occupy multiple stack slots.
IRVariable part(std::string const& _slot) const;
private:
/// @returns a vector containing the names of the stack slots of the variable.
std::vector<std::string> stackSlots() const;
/// @returns a name consisting of the base name appended with an underscore and @æ _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 const& m_type;
};
}

View File

@ -34,8 +34,8 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
function fun_f_9() -> vloc__4_mpos {
vloc__4_mpos := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
leave
}
@ -130,8 +130,8 @@ object \"C_10\" {
}
}
function fun_f_9() -> vloc__4 {
vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
function fun_f_9() -> vloc__4_mpos {
vloc__4_mpos := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr()
leave
}

View File

@ -38,8 +38,8 @@ object \"C_10\" {
}
function fun_f_9() -> vloc__4 {
vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
function fun_f_9() -> vloc__4_mpos {
vloc__4_mpos := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
leave
}
@ -138,8 +138,8 @@ object \"C_10\" {
}
}
function fun_f_9() -> vloc__4 {
vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
function fun_f_9() -> vloc__4_mpos {
vloc__4_mpos := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr()
leave
}

View File

@ -0,0 +1,16 @@
contract C {
function f() public pure returns (uint, uint, uint) {
return (1, 2, 3);
}
function g() public pure returns (uint a, uint b, uint c) {
(c, b, a) = f();
}
function h() public pure returns (uint a) {
(,,a) = f();
}
}
// ====
// compileViaYul: also
// ----
// g() -> 3, 2, 1
// h() -> 3

View File

@ -0,0 +1,18 @@
contract C {
function f() public pure returns (uint, uint, uint) {
return (1, 2, 3);
}
function g() public pure returns (uint x, uint y, uint z) {
(uint c, uint b, uint a) = f();
(x, y, z) = (a, b, c);
}
function h() public pure returns (uint) {
(,,uint a) = f();
return a;
}
}
// ====
// compileViaYul: also
// ----
// g() -> 3, 2, 1
// h() -> 3

View File

@ -0,0 +1,32 @@
contract C {
uint public x = 17;
function f(uint a1, uint a2) public returns (uint r1, uint r2) {
(uint b1, uint b2) = (a1, a2);
(r1, x, r2) = (b1, b2, b2);
}
function g() public returns (uint a, uint b, uint c) {
uint256[3] memory m;
(m[0], m[1], m[2]) = (1, x, 3);
return (m[2], m[1], m[0]);
}
function h() public returns (uint a, uint b, uint c) {
uint256[3] memory m;
(m[0], m[1], , m[2], m[0]) = (1, x, 3, 4, 42);
return (m[2], m[1], m[0]);
}
function i() public returns (uint a, uint b, uint c, uint d) {
(a) = 42;
(((((b))))) = 23;
c = (((17)));
(((d))) = (13);
}
}
// ====
// compileViaYul: also
// ----
// x() -> 17
// f(uint256,uint256): 23, 42 -> 23, 42
// x() -> 42
// g() -> 3, 42, 1
// h() -> 4, 42, 1
// i() -> 42, 23, 17, 13

View File

@ -0,0 +1,10 @@
contract C {
function f() public pure returns (uint) {
uint x;
return x;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0

View File

@ -0,0 +1,14 @@
contract C {
uint256 x;
uint256 y;
function set(uint256 v) public returns (uint256) { x = v; return v; }
function f() public returns (uint256, uint256) {
(y, y, y) = (set(1), set(2), set(3));
assert(y == 1 && x == 3);
return (x, y);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 3, 1