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
+87 -2
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));
+5
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;
+23
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;
+7 -1
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)
+4
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, "");
+4
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);
+2
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:
{