Legacy codegeneration for immutable state variables.

This commit is contained in:
Daniel Kirchner 2020-03-10 14:30:04 +01:00 committed by chriseth
parent 83cbfbb7bf
commit 04d8ad2ae1
22 changed files with 471 additions and 17 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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, "");

View File

@ -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);

View File

@ -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:
{

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)

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:

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

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

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();
}

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.");

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;

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);
}

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)
{

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;

View File

@ -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(

View File

@ -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{

View File

@ -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

View File

@ -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

View 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