LValues and state variables of value type.

This commit is contained in:
chriseth 2019-04-30 18:32:56 +02:00
parent dcca6f6318
commit aa4d4afcdc
13 changed files with 382 additions and 25 deletions

View File

@ -73,6 +73,8 @@ set(sources
codegen/ir/IRGeneratorForStatements.h
codegen/ir/IRGenerationContext.cpp
codegen/ir/IRGenerationContext.h
codegen/ir/IRLValue.cpp
codegen/ir/IRLValue.h
formal/EncodingContext.cpp
formal/EncodingContext.h
formal/SMTChecker.cpp

View File

@ -237,6 +237,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
}
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{
solAssert(_numBytes <= 32, "");
solAssert(_shiftBytes <= 32, "");
size_t numBits = _numBytes * 8;
size_t shiftBits = _shiftBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, toInsert) -> result {
let mask := <mask>
toInsert := <shl>(toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits))
("shl", shiftLeftFunction(shiftBits))
.render();
});
}
string YulUtilFunctions::roundUpFunction()
{
string functionName = "round_up_to_mul_of_32";
@ -541,6 +565,26 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
});
}
string YulUtilFunctions::prepareStoreFunction(Type const& _type)
{
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> ret {
ret := <actualPrepare>
}
)");
templ("functionName", functionName);
if (_type.category() == Type::Category::FixedBytes)
templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)");
else
templ("actualPrepare", "value");
return templ.render();
});
}
string YulUtilFunctions::allocationFunction()
{
string functionName = "allocateMemory";

View File

@ -69,6 +69,11 @@ public:
std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(size_t _numBits);
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
/// byte) by the _numBytes least significant bytes of `toInsert`.
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
/// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction();
@ -110,6 +115,12 @@ public:
/// single variable.
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
/// @returns the name of a function that prepares a value of the given type
/// for being stored in storage. This usually includes cleanup and right-alignment
/// to fit the number of bytes in storage.
/// The resulting value might still have dirty higher order bits.
std::string prepareStoreFunction(Type const& _type);
/// @returns the name of a function that allocates memory.
/// Modifies the "free memory pointer"
/// Arguments: size

View File

@ -39,7 +39,7 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
{
solAssert(
m_localVariables.count(&_varDecl),
@ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
return m_localVariables[&_varDecl];
}
void IRGenerationContext::addStateVariable(
VariableDeclaration const& _declaration,
u256 _storageOffset,
unsigned _byteOffset
)
{
m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset);
}
string IRGenerationContext::functionName(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
@ -128,3 +137,8 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
return templ.render();
});
}
YulUtilFunctions IRGenerationContext::utils()
{
return YulUtilFunctions(m_evmVersion, m_functions);
}

View File

@ -26,6 +26,8 @@
#include <liblangutil/EVMVersion.h>
#include <libdevcore/Common.h>
#include <string>
#include <memory>
#include <vector>
@ -39,6 +41,7 @@ class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
class YulUtilFunctions;
/**
* Class that contains contextual information during IR generation.
@ -62,7 +65,16 @@ public:
std::string addLocalVariable(VariableDeclaration const& _varDecl);
std::string variableName(VariableDeclaration const& _varDecl);
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
std::string localVariableName(VariableDeclaration const& _varDecl);
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const
{
return m_stateVariables.at(&_varDecl);
}
std::string functionName(FunctionDefinition const& _function);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
@ -74,11 +86,16 @@ public:
std::string internalDispatch(size_t _in, size_t _out);
/// @returns a new copy of the utility function generator (but using the same function set).
YulUtilFunctions utils();
private:
langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
size_t m_varCounter = 0;
};

View File

@ -89,15 +89,14 @@ string IRGenerator::generate(ContractDefinition const& _contract)
}
)");
resetContext();
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
t("deploy", deployCode(_contract));
t("functions", m_context.functionCollector()->requestedFunctions());
resetContext();
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
@ -249,7 +248,7 @@ string IRGenerator::memoryInit()
.render();
}
void IRGenerator::resetContext()
void IRGenerator::resetContext(ContractDefinition const& _contract)
{
solAssert(
m_context.functionCollector()->requestedFunctions().empty(),
@ -257,4 +256,8 @@ void IRGenerator::resetContext()
);
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables())
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}

View File

@ -68,7 +68,7 @@ private:
std::string memoryInit();
void resetContext();
void resetContext(ContractDefinition const& _contract);
langutil::EVMVersion const m_evmVersion;
OptimiserSettings const m_optimiserSettings;

View File

@ -21,6 +21,7 @@
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/TypeProvider.h>
@ -66,7 +67,7 @@ struct CopyTranslate: public yul::ASTCopier
return yul::Identifier{
_identifier.location,
yul::YulString{m_context.variableName(*varDecl)}
yul::YulString{m_context.localVariableName(*varDecl)}
};
}
@ -79,6 +80,12 @@ private:
string IRGeneratorForStatements::code() const
{
solAssert(!m_currentLValue, "LValue not reset!");
return m_code.str();
}
bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement)
{
for (auto const& decl: _varDeclStatement.declarations())
@ -94,7 +101,7 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDec
VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front();
m_code <<
"let " <<
m_context.variableName(varDecl) <<
m_context.localVariableName(varDecl) <<
" := " <<
expressionAsType(*expression, *varDecl.type()) <<
"\n";
@ -102,7 +109,7 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDec
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
m_code << "let " << m_context.variableName(*decl) << "\n";
m_code << "let " << m_context.localVariableName(*decl) << "\n";
return false;
}
@ -112,17 +119,18 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
solUnimplementedAssert(_assignment.assignmentOperator() == Token::Assign, "");
_assignment.rightHandSide().accept(*this);
Type const* intermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType(
_assignment.leftHandSide().annotation().type
);
string intermediateValue = m_context.newYulVariable();
m_code << "let " << intermediateValue << " := " << expressionAsType(_assignment.rightHandSide(), *intermediateType) << "\n";
// TODO proper lvalue handling
auto const& lvalue = dynamic_cast<Identifier const&>(_assignment.leftHandSide());
string varName = m_context.variableName(dynamic_cast<VariableDeclaration const&>(*lvalue.annotation().referencedDeclaration));
_assignment.leftHandSide().accept(*this);
solAssert(!!m_currentLValue, "LValue not retrieved.");
m_currentLValue->storeValue(intermediateValue, *intermediateType);
m_currentLValue.reset();
m_code <<
varName <<
" := " <<
expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) <<
"\n";
defineExpression(_assignment) << varName << "\n";
defineExpression(_assignment) << intermediateValue << "\n";
return false;
}
@ -179,7 +187,7 @@ bool IRGeneratorForStatements::visit(Return const& _return)
// TODO support tuples
solUnimplementedAssert(types.size() == 1, "Multi-returns not implemented.");
m_code <<
m_context.variableName(*returnParameters.front()) <<
m_context.localVariableName(*returnParameters.front()) <<
" := " <<
expressionAsType(*value, *types.front()) <<
"\n";
@ -329,14 +337,26 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
string value;
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
value = to_string(m_context.virtualFunction(*functionDef).id());
defineExpression(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
value = m_context.variableName(*varDecl);
{
// TODO for the constant case, we have to be careful:
// If the value is visited twice, `defineExpression` is called twice on
// the same expression.
solUnimplementedAssert(!varDecl->isConstant(), "");
unique_ptr<IRLValue> lvalue;
if (m_context.isLocalVariable(*varDecl))
lvalue = make_unique<IRLocalVariable>(m_code, m_context, *varDecl);
else if (m_context.isStateVariable(*varDecl))
lvalue = make_unique<IRStorageItem>(m_code, m_context, *varDecl);
else
solAssert(false, "Invalid variable kind.");
setLValue(_identifier, move(lvalue));
}
else
solUnimplemented("");
defineExpression(_identifier) << value << "\n";
return false;
}
@ -375,3 +395,14 @@ ostream& IRGeneratorForStatements::defineExpression(Expression const& _expressio
{
return m_code << "let " << m_context.variable(_expression) << " := ";
}
void IRGeneratorForStatements::setLValue(Expression const& _expression, unique_ptr<IRLValue> _lvalue)
{
solAssert(!m_currentLValue, "");
if (_expression.annotation().lValueRequested)
// Do not define the expression, so it cannot be used as value.
m_currentLValue = std::move(_lvalue);
else
defineExpression(_expression) << _lvalue->retrieveValue() << "\n";
}

View File

@ -21,6 +21,7 @@
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ir/IRLValue.h>
namespace dev
{
@ -42,7 +43,7 @@ public:
m_utils(_utils)
{}
std::string code() const { return m_code.str(); }
std::string code() const;
bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Assignment const& _assignment) override;
@ -62,9 +63,12 @@ private:
std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue);
std::ostringstream m_code;
IRGenerationContext& m_context;
YulUtilFunctions& m_utils;
std::unique_ptr<IRLValue> m_currentLValue;
};
}

View File

@ -0,0 +1,103 @@
/*
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/ast/AST.h>
#include <libdevcore/Whiskers.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
IRLocalVariable::IRLocalVariable(
ostream& _code,
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_code, _context, _varDecl.annotation().type),
m_variableName(_context.localVariableName(_varDecl))
{
}
void IRLocalVariable::storeValue(string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Storing different types - not necessarily a problem.");
m_code << m_variableName << " := " << _value << "\n";
}
IRStorageItem::IRStorageItem(
ostream& _code,
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_code, _context, _varDecl.annotation().type)
{
u256 slot;
unsigned offset;
std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl);
m_slot = toCompactHexWithPrefix(slot);
m_offset = offset;
}
string IRStorageItem::retrieveValue() const
{
if (!m_type->isValueType())
return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")";
}
void IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{
if (m_type->isValueType())
{
solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() + m_offset <= 32, "");
solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
m_code <<
Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n")
("slot", m_slot)
("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset))
("prepare", m_context.utils().prepareStoreFunction(*m_type))
("value", _value)
.render();
}
else
{
solAssert(
_sourceType.category() == m_type->category(),
"Wrong type conversation for assignment."
);
if (m_type->category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (m_type->category() == Type::Category::Struct)
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
}

View File

@ -0,0 +1,95 @@
/*
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.
*/
#pragma once
#include <string>
#include <ostream>
namespace dev
{
namespace solidity
{
class VariableDeclaration;
class IRGenerationContext;
class Type;
/**
* Abstract class used to retrieve, delete and store data in LValues.
*/
class IRLValue
{
protected:
IRLValue(std::ostream& _code, IRGenerationContext& _context, Type const* _type = nullptr):
m_code(_code),
m_context(_context),
m_type(_type)
{}
public:
virtual ~IRLValue() = default;
/// @returns an expression to retrieve the value of the lvalue. This might also emit
/// code to m_code.
virtual std::string retrieveValue() const = 0;
/// Appends code to m_code to store the value of @a _value (should be an identifier)
/// of type @a _type in the lvalue. Might perform type conversion.
virtual void storeValue(std::string const& _value, Type const& _type) const = 0;
protected:
std::ostream& m_code;
IRGenerationContext& m_context;
Type const* m_type;
};
class IRLocalVariable: public IRLValue
{
public:
IRLocalVariable(
std::ostream& _code,
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
std::string retrieveValue() const override { return m_variableName; }
void storeValue(std::string const& _value, Type const& _type) const override;
private:
std::string m_variableName;
};
class IRStorageItem: public IRLValue
{
public:
IRStorageItem(
std::ostream& _code,
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
std::string retrieveValue() const override;
void storeValue(std::string const& _value, Type const& _type) const override;
private:
std::string m_slot;
unsigned m_offset;
};
}
}

View File

@ -0,0 +1,16 @@
contract C {
uint16 x;
byte y;
uint16 z;
function f(uint8 a) public returns (uint _x) {
x = a;
y = byte(uint8(x) + 1);
z = uint8(y) + 1;
x = z + 1;
_x = x;
}
}
// ====
// compileViaYul: true
// ----
// f(uint8): 6 -> 9

View File

@ -0,0 +1,17 @@
contract C {
uint x;
uint y;
function setX(uint a) public returns (uint _x) {
x = a;
_x = x;
}
function setY(uint a) public returns (uint _y) {
y = a;
_y = y;
}
}
// ====
// compileViaYul: true
// ----
// setX(uint256): 6 -> 6
// setY(uint256): 2 -> 2