mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Legacy codegeneration for immutable state variables.
This commit is contained in:
committed by
chriseth
parent
83cbfbb7bf
commit
04d8ad2ae1
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user