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:
parent
83cbfbb7bf
commit
04d8ad2ae1
@ -283,6 +283,24 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
||||
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
|
||||
);
|
||||
break;
|
||||
case PushImmutable:
|
||||
collection.append(createJsonValue(
|
||||
"PUSHIMMUTABLE",
|
||||
sourceIndex,
|
||||
i.location().start,
|
||||
i.location().end,
|
||||
m_immutables.at(h256(i.data()))
|
||||
));
|
||||
break;
|
||||
case AssignImmutable:
|
||||
collection.append(createJsonValue(
|
||||
"ASSIGNIMMUTABLE",
|
||||
sourceIndex,
|
||||
i.location().start,
|
||||
i.location().end,
|
||||
m_immutables.at(h256(i.data()))
|
||||
));
|
||||
break;
|
||||
case Tag:
|
||||
collection.append(
|
||||
createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data())));
|
||||
@ -333,6 +351,20 @@ AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
|
||||
return AssemblyItem{PushLibraryAddress, h};
|
||||
}
|
||||
|
||||
AssemblyItem Assembly::newPushImmutable(string const& _identifier)
|
||||
{
|
||||
h256 h(util::keccak256(_identifier));
|
||||
m_immutables[h] = _identifier;
|
||||
return AssemblyItem{PushImmutable, h};
|
||||
}
|
||||
|
||||
AssemblyItem Assembly::newImmutableAssignment(string const& _identifier)
|
||||
{
|
||||
h256 h(util::keccak256(_identifier));
|
||||
m_immutables[h] = _identifier;
|
||||
return AssemblyItem{AssignImmutable, h};
|
||||
}
|
||||
|
||||
Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs)
|
||||
{
|
||||
OptimiserSettings settings;
|
||||
@ -495,16 +527,44 @@ LinkerObject const& Assembly::assemble() const
|
||||
// Otherwise ensure the object is actually clear.
|
||||
assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references.");
|
||||
|
||||
LinkerObject& ret = m_assembledObject;
|
||||
|
||||
size_t subTagSize = 1;
|
||||
map<u256, vector<size_t>> immutableReferencesBySub;
|
||||
for (auto const& sub: m_subs)
|
||||
{
|
||||
sub->assemble();
|
||||
auto const& linkerObject = sub->assemble();
|
||||
if (!linkerObject.immutableReferences.empty())
|
||||
{
|
||||
assertThrow(
|
||||
immutableReferencesBySub.empty(),
|
||||
AssemblyException,
|
||||
"More than one sub-assembly references immutables."
|
||||
);
|
||||
immutableReferencesBySub = linkerObject.immutableReferences;
|
||||
}
|
||||
for (size_t tagPos: sub->m_tagPositionsInBytecode)
|
||||
if (tagPos != size_t(-1) && tagPos > subTagSize)
|
||||
subTagSize = tagPos;
|
||||
}
|
||||
|
||||
LinkerObject& ret = m_assembledObject;
|
||||
bool setsImmutables = false;
|
||||
bool pushesImmutables = false;
|
||||
|
||||
for (auto const& i: m_items)
|
||||
if (i.type() == AssignImmutable)
|
||||
{
|
||||
i.setImmutableOccurrences(immutableReferencesBySub[i.data()].size());
|
||||
setsImmutables = true;
|
||||
}
|
||||
else if (i.type() == PushImmutable)
|
||||
pushesImmutables = true;
|
||||
if (setsImmutables || pushesImmutables)
|
||||
assertThrow(
|
||||
setsImmutables != pushesImmutables,
|
||||
AssemblyException,
|
||||
"Cannot push and assign immutables in the same assembly subroutine."
|
||||
);
|
||||
|
||||
size_t bytesRequiredForCode = bytesRequired(subTagSize);
|
||||
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1);
|
||||
@ -598,6 +658,24 @@ LinkerObject const& Assembly::assemble() const
|
||||
ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data());
|
||||
ret.bytecode.resize(ret.bytecode.size() + 20);
|
||||
break;
|
||||
case PushImmutable:
|
||||
ret.bytecode.push_back(uint8_t(Instruction::PUSH32));
|
||||
ret.immutableReferences[i.data()].emplace_back(ret.bytecode.size());
|
||||
ret.bytecode.resize(ret.bytecode.size() + 32);
|
||||
break;
|
||||
case AssignImmutable:
|
||||
for (auto const& offset: immutableReferencesBySub[i.data()])
|
||||
{
|
||||
ret.bytecode.push_back(uint8_t(Instruction::DUP1));
|
||||
// TODO: should we make use of the constant optimizer methods for pushing the offsets?
|
||||
bytes offsetBytes = toCompactBigEndian(u256(offset));
|
||||
ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size());
|
||||
ret.bytecode += offsetBytes;
|
||||
ret.bytecode.push_back(uint8_t(Instruction::MSTORE));
|
||||
}
|
||||
immutableReferencesBySub.erase(i.data());
|
||||
ret.bytecode.push_back(uint8_t(Instruction::POP));
|
||||
break;
|
||||
case PushDeployTimeAddress:
|
||||
ret.bytecode.push_back(uint8_t(Instruction::PUSH20));
|
||||
ret.bytecode.resize(ret.bytecode.size() + 20);
|
||||
@ -615,6 +693,13 @@ LinkerObject const& Assembly::assemble() const
|
||||
}
|
||||
}
|
||||
|
||||
assertThrow(
|
||||
immutableReferencesBySub.empty(),
|
||||
AssemblyException,
|
||||
"Some immutables were read from but never assigned."
|
||||
);
|
||||
|
||||
|
||||
if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())
|
||||
// Append an INVALID here to help tests find miscompilation.
|
||||
ret.bytecode.push_back(uint8_t(Instruction::INVALID));
|
||||
|
@ -54,6 +54,8 @@ public:
|
||||
Assembly& sub(size_t _sub) { return *m_subs.at(_sub); }
|
||||
AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
|
||||
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
|
||||
AssemblyItem newPushImmutable(std::string const& _identifier);
|
||||
AssemblyItem newImmutableAssignment(std::string const& _identifier);
|
||||
|
||||
AssemblyItem const& append(AssemblyItem const& _i);
|
||||
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
|
||||
@ -64,6 +66,8 @@ public:
|
||||
/// after compilation and CODESIZE is not an option.
|
||||
void appendProgramSize() { append(AssemblyItem(PushProgramSize)); }
|
||||
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
|
||||
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
|
||||
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
|
||||
|
||||
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
|
||||
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
|
||||
@ -166,6 +170,7 @@ protected:
|
||||
std::vector<std::shared_ptr<Assembly>> m_subs;
|
||||
std::map<util::h256, std::string> m_strings;
|
||||
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
|
||||
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
|
||||
|
||||
mutable LinkerObject m_assembledObject;
|
||||
mutable std::vector<size_t> m_tagPositionsInBytecode;
|
||||
|
@ -80,6 +80,13 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
case PushLibraryAddress:
|
||||
case PushDeployTimeAddress:
|
||||
return 1 + 20;
|
||||
case PushImmutable:
|
||||
return 1 + 32;
|
||||
case AssignImmutable:
|
||||
if (m_immutableOccurrences)
|
||||
return 1 + (3 + 32) * *m_immutableOccurrences;
|
||||
else
|
||||
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -90,6 +97,8 @@ int AssemblyItem::arguments() const
|
||||
{
|
||||
if (type() == Operation)
|
||||
return instructionInfo(instruction()).args;
|
||||
else if (type() == AssignImmutable)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
@ -108,6 +117,7 @@ int AssemblyItem::returnValues() const
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
case PushLibraryAddress:
|
||||
case PushImmutable:
|
||||
case PushDeployTimeAddress:
|
||||
return 1;
|
||||
case Tag:
|
||||
@ -135,6 +145,7 @@ bool AssemblyItem::canBeFunctional() const
|
||||
case PushProgramSize:
|
||||
case PushLibraryAddress:
|
||||
case PushDeployTimeAddress:
|
||||
case PushImmutable:
|
||||
return true;
|
||||
case Tag:
|
||||
return false;
|
||||
@ -210,6 +221,12 @@ string AssemblyItem::toAssemblyText() const
|
||||
case PushDeployTimeAddress:
|
||||
text = string("deployTimeAddress()");
|
||||
break;
|
||||
case PushImmutable:
|
||||
text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
|
||||
break;
|
||||
case AssignImmutable:
|
||||
text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
|
||||
break;
|
||||
case UndefinedItem:
|
||||
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
||||
break;
|
||||
@ -275,6 +292,12 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
|
||||
case PushDeployTimeAddress:
|
||||
_out << " PushDeployTimeAddress";
|
||||
break;
|
||||
case PushImmutable:
|
||||
_out << " PushImmutable";
|
||||
break;
|
||||
case AssignImmutable:
|
||||
_out << " AssignImmutable";
|
||||
break;
|
||||
case UndefinedItem:
|
||||
_out << " ???";
|
||||
break;
|
||||
|
@ -44,7 +44,9 @@ enum AssemblyItemType {
|
||||
Tag,
|
||||
PushData,
|
||||
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
|
||||
PushDeployTimeAddress ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
|
||||
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
|
||||
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
|
||||
AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
|
||||
};
|
||||
|
||||
class Assembly;
|
||||
@ -153,6 +155,8 @@ public:
|
||||
|
||||
size_t m_modifierDepth = 0;
|
||||
|
||||
void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared<size_t>(_n); }
|
||||
|
||||
private:
|
||||
AssemblyItemType m_type;
|
||||
Instruction m_instruction; ///< Only valid if m_type == Operation
|
||||
@ -162,6 +166,8 @@ private:
|
||||
/// Pushed value for operations with data to be determined during assembly stage,
|
||||
/// e.g. PushSubSize, PushTag, PushSub, etc.
|
||||
mutable std::shared_ptr<u256> m_pushedValue;
|
||||
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
|
||||
mutable std::shared_ptr<size_t> m_immutableOccurrences;
|
||||
};
|
||||
|
||||
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
|
||||
|
@ -91,6 +91,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
|
||||
{
|
||||
// can be ignored
|
||||
}
|
||||
else if (_item.type() == AssignImmutable)
|
||||
// Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which
|
||||
// is the same as POP.
|
||||
return feedItem(AssemblyItem(Instruction::POP), _copyItem);
|
||||
else if (_item.type() != Operation)
|
||||
{
|
||||
assertThrow(_item.deposit() == 1, InvalidDeposit, "");
|
||||
|
@ -40,6 +40,10 @@ struct LinkerObject
|
||||
/// need to be replaced by the actual addresses by the linker.
|
||||
std::map<size_t, std::string> linkReferences;
|
||||
|
||||
/// Map from hashes of the identifiers of immutable variables to a list of offsets into the bytecode
|
||||
/// that refer to their values.
|
||||
std::map<u256, std::vector<size_t>> immutableReferences;
|
||||
|
||||
/// Appends the bytecode of @a _other and incorporates its link references.
|
||||
void append(LinkerObject const& _other);
|
||||
|
||||
|
@ -36,6 +36,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
|
||||
case UndefinedItem:
|
||||
case Tag:
|
||||
case PushDeployTimeAddress:
|
||||
case AssignImmutable:
|
||||
return true;
|
||||
case Push:
|
||||
case PushString:
|
||||
@ -45,6 +46,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
|
||||
case PushProgramSize:
|
||||
case PushData:
|
||||
case PushLibraryAddress:
|
||||
case PushImmutable:
|
||||
return false;
|
||||
case Operation:
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -203,6 +203,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
|
||||
EthAssemblyAdapter adapter(assembly);
|
||||
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
|
||||
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
|
||||
yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables.");
|
||||
object.assembly = assembly.assemblyString();
|
||||
object.sourceMappings = make_unique<string>(
|
||||
evmasm::AssemblyItem::computeSourceMapping(
|
||||
|
@ -112,6 +112,44 @@ namespace
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(Optimiser)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cse_push_immutable_same)
|
||||
{
|
||||
AssemblyItem pushImmutable{PushImmutable, 0x1234};
|
||||
checkCSE({pushImmutable, pushImmutable}, {pushImmutable, Instruction::DUP1});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cse_push_immutable_different)
|
||||
{
|
||||
AssemblyItems input{{PushImmutable, 0x1234},{PushImmutable, 0xABCD}};
|
||||
checkCSE(input, input);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cse_assign_immutable)
|
||||
{
|
||||
{
|
||||
AssemblyItems input{u256(0x42), {AssignImmutable, 0x1234}};
|
||||
checkCSE(input, input);
|
||||
}
|
||||
{
|
||||
AssemblyItems input{{AssignImmutable, 0x1234}};
|
||||
checkCSE(input, input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
|
||||
{
|
||||
AssemblyItems input = addDummyLocations(AssemblyItems{
|
||||
u256(0x42),
|
||||
{AssignImmutable, 0x1234},
|
||||
Instruction::ORIGIN
|
||||
});
|
||||
|
||||
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()};
|
||||
// Make sure CSE breaks after AssignImmutable.
|
||||
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
|
||||
{
|
||||
evmasm::KnownState state;
|
||||
@ -798,6 +836,68 @@ BOOST_AUTO_TEST_CASE(block_deduplicator)
|
||||
BOOST_CHECK_EQUAL(pushTags.size(), 2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_same)
|
||||
{
|
||||
AssemblyItems blocks{
|
||||
AssemblyItem(Tag, 1),
|
||||
u256(42),
|
||||
AssemblyItem{AssignImmutable, 0x1234},
|
||||
Instruction::JUMP,
|
||||
AssemblyItem(Tag, 2),
|
||||
u256(42),
|
||||
AssemblyItem{AssignImmutable, 0x1234},
|
||||
Instruction::JUMP
|
||||
};
|
||||
|
||||
AssemblyItems input = AssemblyItems{
|
||||
AssemblyItem(PushTag, 2),
|
||||
AssemblyItem(PushTag, 1),
|
||||
} + blocks;
|
||||
AssemblyItems output = AssemblyItems{
|
||||
AssemblyItem(PushTag, 1),
|
||||
AssemblyItem(PushTag, 1),
|
||||
} + blocks;
|
||||
BlockDeduplicator dedup(input);
|
||||
dedup.deduplicate();
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_value)
|
||||
{
|
||||
AssemblyItems input{
|
||||
AssemblyItem(PushTag, 2),
|
||||
AssemblyItem(PushTag, 1),
|
||||
AssemblyItem(Tag, 1),
|
||||
u256(42),
|
||||
AssemblyItem{AssignImmutable, 0x1234},
|
||||
Instruction::JUMP,
|
||||
AssemblyItem(Tag, 2),
|
||||
u256(23),
|
||||
AssemblyItem{AssignImmutable, 0x1234},
|
||||
Instruction::JUMP
|
||||
};
|
||||
BlockDeduplicator dedup(input);
|
||||
BOOST_CHECK(!dedup.deduplicate());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_hash)
|
||||
{
|
||||
AssemblyItems input{
|
||||
AssemblyItem(PushTag, 2),
|
||||
AssemblyItem(PushTag, 1),
|
||||
AssemblyItem(Tag, 1),
|
||||
u256(42),
|
||||
AssemblyItem{AssignImmutable, 0x1234},
|
||||
Instruction::JUMP,
|
||||
AssemblyItem(Tag, 2),
|
||||
u256(42),
|
||||
AssemblyItem{AssignImmutable, 0xABCD},
|
||||
Instruction::JUMP
|
||||
};
|
||||
BlockDeduplicator dedup(input);
|
||||
BOOST_CHECK(!dedup.deduplicate());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(block_deduplicator_loops)
|
||||
{
|
||||
AssemblyItems input{
|
||||
|
@ -0,0 +1,20 @@
|
||||
contract D {
|
||||
function f() external view returns (uint256) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
contract C {
|
||||
D d;
|
||||
function() external view returns(uint256) immutable z;
|
||||
constructor() public {
|
||||
d = new D();
|
||||
z = d.f;
|
||||
}
|
||||
function f() public view returns (uint256) {
|
||||
assert(z.address == address(d));
|
||||
assert(z.selector == D.f.selector);
|
||||
return z();
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f() -> 42
|
@ -0,0 +1,13 @@
|
||||
contract C {
|
||||
uint256 immutable x;
|
||||
uint256 immutable y;
|
||||
constructor() public {
|
||||
x = 42;
|
||||
y = x;
|
||||
}
|
||||
function f() public view returns (uint256, uint256) {
|
||||
return (x+x,y);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f() -> 84, 42
|
13
test/libsolidity/semanticTests/immutable/stub.sol
Normal file
13
test/libsolidity/semanticTests/immutable/stub.sol
Normal file
@ -0,0 +1,13 @@
|
||||
contract C {
|
||||
uint256 immutable x;
|
||||
uint256 immutable y;
|
||||
constructor() public {
|
||||
x = 42;
|
||||
y = 23;
|
||||
}
|
||||
function f() public view returns (uint256, uint256) {
|
||||
return (x+x,y);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f() -> 84, 23
|
Loading…
Reference in New Issue
Block a user