Legacy codegeneration for immutable state variables.

This commit is contained in:
Daniel Kirchner
2020-03-24 16:45:25 +01:00
committed by chriseth
parent 83cbfbb7bf
commit 04d8ad2ae1
22 changed files with 471 additions and 17 deletions
+10
View File
@@ -2012,6 +2012,16 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<VariableDeclaration const*> ContractType::immutableVariables() const
{
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (variable->immutable())
variables.push_back(variable);
return variables;
}
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
+2
View File
@@ -895,6 +895,8 @@ public:
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
/// @returns a list of all immutable variables (including inherited) of the contract.
std::vector<VariableDeclaration const*> immutableVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
+56
View File
@@ -71,6 +71,55 @@ void CompilerContext::addStateVariable(
m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
}
void CompilerContext::addImmutable(VariableDeclaration const& _variable)
{
solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable.");
solUnimplementedAssert(_variable.annotation().type->isValueType(), "Only immutable variables of value type are supported.");
solAssert(m_runtimeContext, "Attempted to register an immutable variable for runtime code generation.");
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory;
solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap.");
*m_reservedMemory += _variable.annotation().type->memoryHeadSize();
}
size_t CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const
{
solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried.");
solAssert(m_runtimeContext, "Attempted to fetch the memory offset of an immutable variable during runtime code generation.");
return m_immutableVariables.at(&_variable);
}
vector<string> CompilerContext::immutableVariableSlotNames(VariableDeclaration const& _variable)
{
string baseName =
_variable.annotation().contract->fullyQualifiedName() +
"." +
_variable.name() +
" (" +
to_string(_variable.id()) +
")";
solAssert(_variable.annotation().type->sizeOnStack() > 0, "");
if (_variable.annotation().type->sizeOnStack() == 1)
return {baseName};
vector<string> names;
auto collectSlotNames = [&](string const& _baseName, TypePointer type, auto const& _recurse) -> void {
for (auto const& [slot, type]: type->stackItems())
if (type)
_recurse(_baseName + " " + slot, type, _recurse);
else
names.emplace_back(_baseName);
};
collectSlotNames(baseName, _variable.annotation().type, collectSlotNames);
return names;
}
size_t CompilerContext::reservedMemory()
{
solAssert(m_reservedMemory.has_value(), "Reserved memory was used before ");
size_t reservedMemory = *m_reservedMemory;
m_reservedMemory = std::nullopt;
return reservedMemory;
}
void CompilerContext::startFunction(Declaration const& _function)
{
m_functionCompilationQueue.startFunction(_function);
@@ -500,6 +549,13 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
#endif
}
LinkerObject const& CompilerContext::assembledObject() const
{
LinkerObject const& object = m_asm->assemble();
solAssert(object.immutableReferences.empty(), "Leftover immutables.");
return object;
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart
+22 -1
View File
@@ -64,6 +64,7 @@ public:
m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_reservedMemory{0},
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
@@ -80,6 +81,16 @@ public:
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addImmutable(VariableDeclaration const& _declaration);
/// @returns the reserved memory for storing the value of the immutable @a _variable during contract creation.
size_t immutableMemoryOffset(VariableDeclaration const& _variable) const;
/// @returns a list of slot names referring to the stack slots of an immutable variable.
static std::vector<std::string> immutableVariableSlotNames(VariableDeclaration const& _variable);
/// @returns the reserved memory and resets it to mark it as used.
size_t reservedMemory();
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(Declaration const& _declaration);
/// Removes all local variables currently allocated above _stackHeight.
@@ -217,6 +228,10 @@ public:
evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
/// Appends the address (virtual, will be filled in by linker) of a library.
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
/// Appends an immutable variable. The value will be filled in by the constructor.
void appendImmutable(std::string const& _identifier) { m_asm->appendImmutable(_identifier); }
/// Appends an assignment to an immutable variable. Only valid in creation code.
void appendImmutableAssignment(std::string const& _identifier) { m_asm->appendImmutableAssignment(_identifier); }
/// Appends a zero-address that can be replaced by something else at deploy time (if the
/// position in bytecode is known).
void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); }
@@ -282,7 +297,7 @@ public:
return m_asm->assemblyJSON(_indicies);
}
evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
evmasm::LinkerObject const& assembledObject() const;
evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); }
/**
@@ -355,6 +370,12 @@ private:
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Memory offsets reserved for the values of immutable variables during contract creation.
std::map<VariableDeclaration const*, size_t> m_immutableVariables;
/// Total amount of reserved memory. Reserved memory is used to store immutable variables during contract creation.
/// This has to be finalized before initialiseFreeMemoryPointer() is called. That function
/// will reset the optional to verify that.
std::optional<size_t> m_reservedMemory = {0};
/// Offsets of local variables on the stack (relative to stack base).
/// This needs to be a stack because if a modifier contains a local variable and this
/// modifier is applied twice, the position of the variable needs to be restored
+3 -1
View File
@@ -51,7 +51,9 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi
void CompilerUtils::initialiseFreeMemoryPointer()
{
m_context << u256(generalPurposeMemoryStart);
size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, "");
m_context << (u256(generalPurposeMemoryStart) + reservedMemory);
storeFreeMemoryPointer();
}
+26 -1
View File
@@ -130,6 +130,8 @@ void ContractCompiler::initializeContext(
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
if (m_runtimeCompiler)
registerImmutableVariables(_contract);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract);
m_context.resetVisitedNodes(&_contract);
@@ -183,10 +185,26 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
m_context << deployRoutine;
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
ContractType contractType(_contract);
auto const& immutables = contractType.immutableVariables();
// Push all immutable values on the stack.
for (auto const& immutable: immutables)
CompilerUtils(m_context).loadFromMemory(m_context.immutableMemoryOffset(*immutable), *immutable->annotation().type);
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << Instruction::DUP1;
if (immutables.empty())
m_context << Instruction::DUP1;
m_context.pushSubroutineOffset(m_context.runtimeSub());
m_context << u256(0) << Instruction::CODECOPY;
// Assign immutable values from stack in reversed order.
for (auto const& immutable: immutables | boost::adaptors::reversed)
{
auto slotNames = m_context.immutableVariableSlotNames(*immutable);
for (auto&& slotName: slotNames | boost::adaptors::reversed)
m_context.appendImmutableAssignment(slotName);
}
if (!immutables.empty())
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << u256(0) << Instruction::RETURN;
return m_context.runtimeSub();
@@ -521,6 +539,13 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
void ContractCompiler::registerImmutableVariables(ContractDefinition const& _contract)
{
solAssert(m_runtimeCompiler, "Attempted to register immutables for runtime code generation.");
for (auto const& var: ContractType(_contract).immutableVariables())
m_context.addImmutable(*var);
}
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
{
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
+1
View File
@@ -99,6 +99,7 @@ private:
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract);
void registerImmutableVariables(ContractDefinition const& _contract);
void initializeStateVariables(ContractDefinition const& _contract);
bool visit(VariableDeclaration const& _variableDeclaration) override;
+1 -1
View File
@@ -2436,7 +2436,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex
if (_variable.isConstant())
acceptAndConvert(*_variable.value(), *_variable.annotation().type);
else if (_variable.immutable())
solUnimplemented("");
setLValue<ImmutableItem>(_expression, _variable);
else
setLValueFromDeclaration(_variable, _expression);
}
+36
View File
@@ -144,6 +144,42 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
m_context << Instruction::POP;
}
ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable):
LValue(_compilerContext, _variable.annotation().type), m_variable(_variable)
{
solAssert(_variable.immutable(), "");
}
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
{
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time.");
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
m_context.appendImmutable(slotName);
}
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
{
CompilerUtils utils(m_context);
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(_sourceType.isValueType(), "");
utils.convertType(_sourceType, *m_dataType, true);
m_context << m_context.immutableMemoryOffset(m_variable);
if (_move)
utils.moveIntoStack(m_dataType->sizeOnStack());
else
utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack());
utils.storeInMemoryDynamic(*m_dataType, false);
m_context << Instruction::POP;
}
void ImmutableItem::setToZero(SourceLocation const&, bool) const
{
solAssert(false, "Attempted to set immutable variable to zero.");
}
StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
StorageItem(_compilerContext, *_declaration.annotation().type)
{
+35 -10
View File
@@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolutil/Common.h>
#include <liblangutil/SourceLocation.h>
#include <memory>
#include <vector>
@@ -82,12 +83,12 @@ public:
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -108,12 +109,12 @@ public:
MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true);
unsigned sizeOnStack() const override { return 1; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -122,6 +123,30 @@ private:
bool m_padded = false;
};
/**
* Reference to an immutable variable. During contract creation this refers to a location in memory. At the
* end of contract creation the values from these memory locations are copied into all occurrences of the immutable
* variable in the runtime code.
*/
class ImmutableItem: public LValue
{
public:
ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable);
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
VariableDeclaration const& m_variable;
};
/**
* Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>,
* where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied
@@ -136,12 +161,12 @@ public:
StorageItem(CompilerContext& _compilerContext, Type const& _type);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -158,12 +183,12 @@ public:
StorageByteArrayElement(CompilerContext& _compilerContext);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -180,12 +205,12 @@ public:
TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues);
unsigned sizeOnStack() const override;
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;